3
\$\begingroup\$

Even though I have asked a question here related to my app using Swing and creating JPanel, I still have other ones about best practice making applications. I'm using Java 8.

I have this class screening which contains Film and Auditorium classes. At the moment the class looks like this:

Screening.java

package cinema.schedule;

import cinema.Auditorium;
import cinema.film.Film;

import java.time.LocalDateTime;
import java.time.LocalTime;

public class Screening {
    public final Auditorium auditorium;
    public final Film film;

    private float ticketPrice;
    private LocalDateTime startDateTime;

    public Screening(Film film, LocalDateTime startDateTime) {
        this(new Auditorium(), 0, startDateTime, film);
    }

    public Screening(Film film, float ticketPrice, LocalDateTime startDateTime) {
        this(new Auditorium(), ticketPrice, startDateTime, film);
    }

    public Screening(Auditorium auditorium, float ticketPrice, LocalDateTime startDateTime, Film film) {
        this.auditorium = auditorium;
        this.ticketPrice = ticketPrice;
        this.startDateTime = startDateTime;
        this.film = film;
    }

    public float getTicketPrice() {
        return ticketPrice;
    }

    public void setTicketPrice(float ticketPrice) {
        this.ticketPrice = ticketPrice;
    }

    public LocalTime getStartTime() {
        return startDateTime.toLocalTime();
    }

    public void setStartTime(LocalDateTime startDateTime) {
        this.startDateTime = startDateTime;
    }

    @Override
    public String toString() {
        return "Screening{" +
                "switcher=" + auditorium +
                ", film=" + film +
                ", ticketPrice=" + ticketPrice +
                ", startDateTime=" + startDateTime +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if(this == o) return true;
        if(o == null || getClass() != o.getClass()) return false;

        Screening screening = (Screening) o;

        if(Float.compare(screening.ticketPrice, ticketPrice) != 0) return false;
        if(film != null ? !film.equals(screening.film) : screening.film != null) return false;
        if(startDateTime != null ? !startDateTime.equals(screening.startDateTime) : screening.startDateTime != null)
            return false;
        if(auditorium != null ? !auditorium.equals(screening.auditorium) : screening.auditorium != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = auditorium != null ? auditorium.hashCode() : 0;
        result = 31 * result + (film != null ? film.hashCode() : 0);
        result = 31 * result + (ticketPrice != +0.0f ? Float.floatToIntBits(ticketPrice) : 0);
        result = 31 * result + (startDateTime != null ? startDateTime.hashCode() : 0);
        return result;
    }
}

Auditorium.java

package cinema;

public class Auditorium {
    private int totalSeats;
    private int occupiedSeats;

    public static Auditorium of(Auditorium auditorium) {
        Auditorium instance = new Auditorium();
        instance.occupiedSeats = auditorium.occupiedSeats;
        instance.totalSeats = auditorium.totalSeats;
        return instance;
    }

    public Auditorium() {
        this(10);
    }

    public Auditorium(int totalSeats) {
        this.totalSeats = totalSeats;
        this.occupiedSeats = 0;
    }

    public void occupySeats(int count) {
        if (getEmptySeats() >= count) {
            occupiedSeats += count;
        } else {
            throw new IndexOutOfBoundsException("Not enough seats");
        }
    }

    public boolean hasEmptySeats() {
        return !(totalSeats <= occupiedSeats);
    }

    public int getEmptySeats() {
        return totalSeats - occupiedSeats;
    }

    @Override
    public String toString() {
        return "Auditorium{" +
                "totalSeats=" + totalSeats +
                ", occupiedSeats=" + occupiedSeats +
                '}';
    }
}

Film.java

    package cinema.film;

    import javax.swing.*;
    import java.time.Duration;

    public class Film {
        private String title;
        private Genre genre;
        private String description;
        private Duration duration;
        private String pathToImage;
        private Language language;
        private Subtitles subtitles;
        private RecommendedAge recommendedAge;

        public Film() {}

        public Film(String title, Genre genre, String filmLengthInDecimal, Language language, Subtitles subtitles, RecommendedAge recommendedAge, String description, String imageTitle, String pathToDir) {
            this.title = title;
            this.genre = genre;
            this.description = description;
            setLenghtInHours(filmLengthInDecimal);
            this.pathToImage = pathToDir + imageTitle;
            this.language = language;
            this.subtitles = subtitles;
            this.recommendedAge = recommendedAge;
        }

        public Film(Film film) {
            this.title = film.title;
            this.genre = film.genre;
            this.description = film.description;
            this.duration = film.duration;
            this.pathToImage = film.pathToImage;
            this.language = film.language;
            this.subtitles = film.subtitles;
            this.recommendedAge = film.recommendedAge;
        }

        public String getTitle() {
            return title;
        }

        public Genre getGenre() {
            return genre;
        }

        public String getDescription() {
            return description;
        }

        public String getDurationTime() {
            long s = duration.getSeconds();
            return String.format("%d:%02d", s/3600, (s%3600)/60);
        }

        public Duration getDuration() {
            return duration;
        }

        public Language getLanguage() {
            return language;
        }

        public Subtitles getSubtitles() {
            return subtitles;
        }

        public RecommendedAge getRecommendedAge() {
            return recommendedAge;
        }

        public Icon getIcon() {
            return new ImageIcon(pathToImage);
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public void setGenre(String genre) {
            this.genre = Genre.valueOf(genre);
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public void setLenghtInHours(String lengthInHours) {
            double timeInDecimal = Double.parseDouble(lengthInHours);
            int fullHours = ((int) timeInDecimal);
            int minutes = (int) ((timeInDecimal - fullHours + 0.001) * 100);
            this.duration = Duration.ofMinutes((fullHours * 60) + minutes);
        }

        public void setPathToImage(String pathToImage) {
            this.pathToImage = pathToImage;
        }

        public void setLanguage(String language) {
            this.language = Language.valueOf(language);
        }

        public void setSubtitles(String subtitles) {
            this.subtitles = Subtitles.valueOf(subtitles);
        }

        public void setRecommendedAge(String recommendedAge) {
            this.recommendedAge = RecommendedAge.valueOf(recommendedAge);
        }

        @Override
        public String toString() {
            return "Movie{" +
                    "title='" + title + '\'' +
                    ", genre='" + genre + '\'' +
                    ", length='" + getDurationTime() + '\'' +
                    ", imageTitle='" + pathToImage + '\'' +
                    ", language='" + language + '\'' +
                    ", subtitles='" + subtitles + '\'' +
                    ", recommendedAge='" + getRecommendedAge() + '\'' +
                    ", description='" + description + '\'' +
                    '}';
        }

    }
  1. Should I leave it like this or change something about public final instance variables? What are my options?

I'm using it in FilmsPanel.java.

package gui.panel.film;

import cinema.schedule.Screening;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class FilmsPanel extends JPanel {
    private final JPanel container = new JPanel(new GridBagLayout());
    private EventListenerList listenerList = new EventListenerList();
    private List<JButton> filmButton = new ArrayList<>();
    private List<Screening> screeningList;

    public  FilmsPanel(String title) {
        if (title != null) {
            container.setBorder(BorderFactory.createTitledBorder("Films"));
        }
        add(container);
    }

    public void setScreeningSet(List<Screening> screeningList) {
        this.screeningList = screeningList;
        updateDateButtons();
    }

    public void addFilmListener(FilmsListener listener) {
        listenerList.add(FilmsListener.class, listener);

    }

    public void removeFilmListener(FilmsListener listener) {
        listenerList.remove(FilmsListener.class, listener);
    }

    private void updateDateButtons(){
        deleteDateButtons();
        createDateButtons();
    }

    private void deleteDateButtons() {
        filmButton.stream().forEach(container::remove);
        filmButton = new ArrayList<>();
        container.revalidate();
        container.repaint();
    }

    private void createDateButtons() {
        GridBagConstraints gbc = new GridBagConstraints(
                0, 0, // grid x, y
                1, 1, // grid width x, y
                0.3, 0.5, // weight , x, y
                GridBagConstraints.PAGE_START, // anchor
                GridBagConstraints.BOTH, // fill
                new Insets(5, 5, 5, 5),
                0, -100 // ipad x, y
        );

        for(Screening screening : screeningList) {
            JButton button = createScreeningButton(screening);
            button.setToolTipText(getFilmInfo(screening));


            button.addActionListener(e -> {
                        setSelectedButton(button);
                        FilmsEvent event = new FilmsEvent(e, screening);
                        fireFilmEvent(event);
                    }
            );

            container.add(button, gbc);
            filmButton.add(button);

            if(gbc.gridx == 1) {
                gbc.gridx = 0;
                gbc.gridy++;
            } else {
                gbc.gridx++;
            }
        }
    }

    private void setSelectedButton(JButton button) {
        filmButton.stream().forEach(b -> b.setEnabled(true));
        button.setEnabled(false);
    }

    private JButton createScreeningButton(Screening screening) {
        int startHours = screening.getStartTime().getHour();
        int startMinutes = screening.getStartTime().getMinute();
        String fullTitle = "<html>" +
                screening.film.getTitle() +
                "<br /> sakums: " +
                String.format("%02d:%02d", startHours, startMinutes) +
                "<br /> garums: " +
                screening.film.getDurationTime() +
                "<br /> brivas vietas- " +
                screening.auditorium.getEmptySeats() +
                "<br /> biletes cena- " +
                String.format("%.2f", screening.getTicketPrice()) +
                "</html>";
        JButton button = new JButton( fullTitle, screening.film.getIcon());
        button.setDisabledIcon(screening.film.getIcon());
        return button;
    }

    private void fireFilmEvent(FilmsEvent event) {
        Object[] listeners = listenerList.getListenerList();

        for(int index = 0; index < listeners.length; index += 2) {
            if (listeners[index] == FilmsListener.class) {
                ((FilmsListener) listeners[index + 1]).moviesEventOccurred(event);
            }
        }
    }

    private String getFilmInfo(Screening screening) {

        return  "<html>" +
                screening.film.getTitle() +
                ".<br /> zanrs: " +
                screening.film.getGenre() +
                "<br /> Apraksts: " +
                splitString(screening.film.getDescription()) +
                "<br /> garums: " +
                screening.film.getDurationTime() +
                ".<br /> valoda: " +
                screening.film.getLanguage() +
                ".<br /> subtitri: " +
                screening.film.getSubtitles() +
                ".<br /> ieteicamais vecums: " +
                screening.film.getRecommendedAge() +
                "</html>";
    }


    public static String splitString(String string) {
        String buf = "";
        String tempString = string;
        if (string != null) {
            while(tempString.length() > 60)
            {
                String block = tempString.substring(0, 60);
                int index = block.lastIndexOf(' ');
                if(index < 0) {
                    index = tempString.indexOf(' ');
                }
                if (index >= 0){
                    buf += (tempString.substring(0, index) + "<BR>");
                }
                tempString = tempString.substring(index+1);
            }
        }
        else {
            tempString = " ";
        }
        buf += tempString;
        return buf;
    }

}
  1. What are other things you notice about the code that I should change/improve/work on?

  2. What are industry standards for making things similar to this? Should I make interfaces where it assembles for example button title, button image? It seems like a lot of classes for such a little thing.

\$\endgroup\$
  • 1
    \$\begingroup\$ I prefer to make class members "final" by default. Meaning: Only when it is very clear that a member has to change over time; then I avoid using final. I think that using final (for member fields) has two big advantages: the compiler will tell you if you forget to initialize a field; and secondly, it can make multi-threading a lot easier. When dealing with "value" only classes (like String, Integer, ...) ... you don't have to worry about concurrent "write" scenarios of all such field are final. \$\endgroup\$ – GhostCat Apr 12 '15 at 12:03
  • \$\begingroup\$ I feel like there is a huge convention about encapsulating all class members. Should I crate .getAuditorium () and getFilm methods? And I should what would be the purpose of it? \$\endgroup\$ – user255572 Apr 12 '15 at 17:52
4
\$\begingroup\$

I'll leave it to others to for the GUI review, I only have a suggestion for your Screening class:

Constructor arguments positioning

It is sometimes recommended to order constructor arguments by their requirements, e.g.

public Class(A argA) {
    this(argA, defaultArgB); 
}

public Class(A argA, B argB) {
    this(argA, argB, defaultArgsC);
}

public Class(A argA, B argB, C... argsC) {
    ...
}

The benefits of doing so are:

  • It is easier to remember what arguments must be supplied, and what has default values.
  • It is easier to 'swap' constructors, e.g. during testing, by simply adding or removing the last argument.
  • If any arguments are expected to be in an array form, you can make use of the varargs feature (see final example above for C... argsC) to slightly simplify the declaration.
  • It helps to prevent careless mistakes when you have the same or convertible argument types. E.g. if you happen to be taking a bunch of Strings. Assume type B extends A and if the second example above was written as such:

    public Class(B argB, A argA) {
        this(argA, argB, defaultArgsC);
    }
    

    If you accidentally remove the second argument of such a constructor call: new Class(objB, objA) to just new Class(objB), objB gets up-casted and is treated like any instance of A instead. This may make understanding the code at 3 am harder... of course, if there's no relationship between these two types, you'll get a straightforward compilation error immediately, which is still bad at 3 am...

Other possible areas for improvements

  • Your hashCode() and equals() implementations for Screening appears to be generated by your IDE of choice, but in any case Java 7 already has Objects.hash() and Objects.deepEquals(), so you may want to consider using them instead.
  • I don't have much experience with the new Time APIs of Java 8, but I have a feeling some of your calculations or formatting (see getDurationTime() and setLenghtInHours() in Film) can be easier done without having to perform the calculations yourself.
  • Auditorium.of() seems to be unused, consider removing it?
    • Speaking of your Auditorium class, I find it unusual that its usage seem to necessitate the creation of new instances quite often, such as whenever you have a new Screening (without specifying the auditorium argument). Your may want to review your model too...
\$\endgroup\$
  • \$\begingroup\$ Thanks for advices and correcting me of me being sloppy. Also about those unused methods: should I write unused methods if I fill it is like plus and minus? For example I would most of the time write get and set or add and remove methods if I think there is no reason no to do it. Any other advice? What would you do differently except this? \$\endgroup\$ – user255572 Apr 12 '15 at 18:08
  • 2
    \$\begingroup\$ If getters and setters are generated, then I think it's fine. The less code you write upfront, the less chances of bugs. :) Related note: You Aren't Gonna Need It (YAGNI) \$\endgroup\$ – h.j.k. Apr 13 '15 at 1:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.