Wie kann ich eleganter zwischen Logik und grafischer Oberfläche trennen?


#1

Einleitung

Ich trenne für meine Projekte mit grafischer Oberfläche gerne die logischen Teile von denen, die sich um die GUI kümmern.

Dabei entspricht meine Logik wohl dem Controller aus dem MVC-Pattern und stellt damit die Verbindung zwischen den Daten (dem Modell) und der View dar. Er enthält im Normalfall die Daten (die in eigenen Klassen liegen) und eine Verbindung zur Gui.

Die Gui (entspricht der View aus dem MVC-Pattern) bekommt wiederum die Logik “injiziert” (sprich im Konstruktor übergeben), um in ActionListern von Buttons oder ähnlichem die inhaltliche Arbeit an die Logik zu übergeben.

Paketstruktur der Beispiele

Die Struktur der Beispiele in meinem extra für dieses Beispiel erstellte Projekt (was auch die kurzen Paketnamen erklären mag) sieht wie folgt aus:

[ul]
[li] complex[/li] [LIST]
[li] logic[/li] [li] ui[/li] [/ul]
[li] data[/li] [li] simple[/li] [ul]
[li] logic[/li] [li] ui[/li] [/ul]
[li] start[/li][/LIST]

Daten für die Beispiele

Als Daten (entspricht dem Model im MVC-Pattern) verwende ich in den Beispielen eine Person mit den folgenden exemplarischen Daten:

[ul]
[li] Vorname[/li] [li] Nachname[/li] [li] Geburtstag[/li] [li] Straße[/li] [li] Postleitzahl[/li] [li] Ort[/li] [li] Land[/li][/ul]

Die Klasse data.Person enthält neben dem Konstruktor die Getter und einige Hilfsmethoden, weil ich unveränderliche Objekte mag.
[ot]Dinge wie equals() und hashCode(), die diese Klassen normalerweise haben, lasse ich hier weg, weil sie in den Beispielen nicht gebraucht werden.[/ot]
So sieht sie aus:


public class Person {

    private final String forename;
    private final String surname;
    private final String birthday;
    private final String street;
    private final String postalCode;
    private final String town;
    private final String country;

    public Person(String forename, String surname, String birthday,
            String street, String postalCode, String town, String country) {
        this.forename = forename;
        this.surname = surname;
        this.birthday = birthday;
        this.street = street;
        this.postalCode = postalCode;
        this.town = town;
        this.country = country;
    }

    public String getForename() {
        return forename;
    }

    public String getSurname() {
        return surname;
    }

    public String getBirthday() {
        return birthday;
    }

    public String getStreet() {
        return street;
    }

    public String getPostalCode() {
        return postalCode;
    }

    public String getTown() {
        return town;
    }

    public String getCountry() {
        return country;
    }

    @Override
    public String toString() {
        return "Person [forename=" + forename + ", surname=" + surname
                + ", birthday=" + birthday + ", street=" + street
                + ", postalCode=" + postalCode + ", town=" + town
                + ", country=" + country + "]";
    }

    public static Person createWithNewForename(Person person, String forename) {
        return new Person(forename, person.getSurname(), person.getBirthday(),
                person.getStreet(), person.getPostalCode(), person.getTown(),
                person.getCountry());
    }

    public static Person createWithNewSurname(Person person, String surname) {
        return new Person(person.getForename(), surname, person.getBirthday(),
                person.getStreet(), person.getPostalCode(), person.getTown(),
                person.getCountry());
    }

    public static Person createWithNewBirthday(Person person, String birthday) {
        return new Person(person.getForename(), person.getSurname(), birthday,
                person.getStreet(), person.getPostalCode(), person.getTown(),
                person.getCountry());
    }

    public static Person createWithNewStreet(Person person, String street) {
        return new Person(person.getForename(), person.getSurname(),
                person.getBirthday(), street, person.getPostalCode(),
                person.getTown(), person.getCountry());
    }

    public static Person createWithNewPostalCode(Person person,
            String postalCode) {
        return new Person(person.getForename(), person.getSurname(),
                person.getBirthday(), person.getStreet(), postalCode,
                person.getTown(), person.getCountry());
    }

    public static Person createWithNewTown(Person person, String town) {
        return new Person(person.getForename(), person.getSurname(),
                person.getBirthday(), person.getStreet(),
                person.getPostalCode(), town, person.getCountry());
    }

    public static Person createWithNewCountry(Person person, String country) {
        return new Person(person.getForename(), person.getSurname(),
                person.getBirthday(), person.getStreet(),
                person.getPostalCode(), person.getTown(), country);
    }

}```


Einfaches Beispiel



Das einfache Beispiel besteht aus einer Logik- und einer Guiklasse sowie einer Klasse, die das ganze startet. Dem Beispielcharakter geschuldet erfolgen die Ausgaben rundherum schlicht auf die Standardausgabe.

Start des einfachen Beispiels

Den Startpunkt des einfachen Beispielprogramms bildet die Klasse `start.SimpleLogicAndGuiExample`:

```package start;

import data.Person;
import simple.logic.SimpleLogic;

public class SimpleLogicAndGuiExample {

    public static void main(String[] args) {
        Person person = new Person("Max", "Mustermann", "01.01.1990",
                "Musterstraße 123", "12345", "Musterhausen", "Deutschland");

        System.out.println("Zu Beginn:
    " + person);

        SimpleLogic logic = new SimpleLogic(person);
        logic.start();
    }

}```

Logik des einfachen Beispiels

Die Logik des einfachen Beispielprogramms bildet die Klasse `simple.logic.SimpleLogic`:

```package simple.logic;

import data.Person;
import simple.ui.SimpleGui;

public class SimpleLogic {

    private final SimpleGui gui;

    private Person person;

    public SimpleLogic(final Person person) {
        this.person = person;
        gui = new SimpleGui(this);
    }

    public void start() {
        gui.show();
    }

    public String getForename() {
        return person.getForename();
    }

    public String getSurname() {
        return person.getSurname();
    }

    public String getBirthday() {
        return person.getBirthday();
    }

    public String getStreet() {
        return person.getStreet();
    }

    public String getPostalCode() {
        return person.getPostalCode();
    }

    public String getTown() {
        return person.getTown();
    }

    public String getCountry() {
        return person.getCountry();
    }

    public void setForename(final String forename) {
        person = Person.createWithNewForename(person, forename);
    }

    public void setSurname(final String surname) {
        person = Person.createWithNewSurname(person, surname);
    }

    public void setBirthday(final String birthday) {
        person = Person.createWithNewBirthday(person, birthday);
    }

    public void setStreet(final String street) {
        person = Person.createWithNewStreet(person, street);
    }

    public void setPostalCode(final String postalCode) {
        person = Person.createWithNewPostalCode(person, postalCode);
    }

    public void setTown(final String town) {
        person = Person.createWithNewTown(person, town);
    }

    public void setCountry(final String country) {
        person = Person.createWithNewCountry(person, country);
    }

    public void quitPressed() {
        System.out.println("Nach Klick auf Quit:
    " + person);
        quit();
    }

    private void quit() {
        gui.quit();
    }

    public void okPressed() {
        System.out.println("Nach Klick auf OK:
    " + person);
        quit();
    }

}```

Oberfläche des einfachen Beispiels

Die grafische Oberfläche des einfachen Beispielprogramms bildet die Klasse `simple.ui.SimpleGui`:

```package simple.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import simple.logic.SimpleLogic;

public class SimpleGui {

    private static final int LABEL_WIDTH = 100;
    private static final int FIELD_WIDTH = 300;
    private static final int FIELD_HIGHT = 30;

    private final SimpleLogic logic;

    private final JFrame frame;

    private final JTextField forenameField;
    private final JTextField surnameField;
    private final JTextField birthdayField;
    private final JTextField streetField;
    private final JTextField postalCodeField;
    private final JTextField townField;
    private final JTextField countryField;

    public SimpleGui(final SimpleLogic logic) {
        this.logic = logic;

        setNiceLayoutManager();

        frame = new JFrame();
        forenameField = new JTextField();
        surnameField = new JTextField();
        birthdayField = new JTextField();
        streetField = new JTextField();
        postalCodeField = new JTextField();
        townField = new JTextField();
        countryField = new JTextField();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
                loadPersonDataIntoFields();
            }
        });
    }

    private static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createGui() {
        frame.setTitle("Einfaches Beispiel für Logik und Gui");
        frame.setLayout(new BorderLayout());

        frame.add(createAllButton(), BorderLayout.SOUTH);
        frame.add(createDataPart(), BorderLayout.CENTER);

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private Component createAllButton() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createQuitButton(), BorderLayout.WEST);
        panel.add(createGoodButtons(), BorderLayout.EAST);

        return panel;
    }

    private Component createGoodButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createApplyButton(), BorderLayout.WEST);
        panel.add(createOkButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createApplyButton() {
        JButton applyButton = new JButton("Apply");
        applyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changePersonData();
            }
        });
        return applyButton;
    }

    private Component createOkButton() {
        JButton okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changePersonData();
                logic.okPressed();
            }
        });
        return okButton;
    }

    private void changePersonData() {
        logic.setForename(forenameField.getText());
        logic.setSurname(surnameField.getText());
        logic.setBirthday(birthdayField.getText());
        logic.setStreet(streetField.getText());
        logic.setPostalCode(postalCodeField.getText());
        logic.setTown(townField.getText());
        logic.setCountry(countryField.getText());
    }

    private Component createQuitButton() {
        JButton quitButton = new JButton("Quit");
        quitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.quitPressed();
            }
        });
        return quitButton;
    }

    private Component createDataPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createDataButtons(), BorderLayout.EAST);
        panel.add(createDataArea(), BorderLayout.CENTER);

        return panel;
    }

    private Component createDataArea() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(0, 1, 5, 5));
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createFieldPanel("Nachname", forenameField));
        panel.add(createFieldPanel("Vorname", surnameField));
        panel.add(createFieldPanel("Geburtsdatum", birthdayField));
        panel.add(createFieldPanel("Straße", streetField));
        panel.add(createFieldPanel("Postleitzahl", postalCodeField));
        panel.add(createFieldPanel("Ort", townField));
        panel.add(createFieldPanel("Land", countryField));

        return panel;
    }

    private Component createFieldPanel(String labelText, JTextField field) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JLabel label = new JLabel(labelText + ":");

        label.setPreferredSize(new Dimension(LABEL_WIDTH, FIELD_HIGHT));
        field.setPreferredSize(new Dimension(FIELD_WIDTH, FIELD_HIGHT));

        panel.add(label, BorderLayout.WEST);
        panel.add(field, BorderLayout.CENTER);

        return panel;
    }

    private Component createDataButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        JPanel subpanel = new JPanel();
        subpanel.setLayout(new BorderLayout());

        subpanel.add(createClearAllButton(), BorderLayout.NORTH);
        subpanel.add(createReloadButton(), BorderLayout.SOUTH);
        panel.add(subpanel, BorderLayout.NORTH);

        return panel;
    }

    private Component createClearAllButton() {
        JButton clearAllButton = new JButton("clear");
        clearAllButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                forenameField.setText("");
                surnameField.setText("");
                birthdayField.setText("");
                streetField.setText("");
                postalCodeField.setText("");
                townField.setText("");
                countryField.setText("");
            }
        });
        return clearAllButton;
    }

    private Component createReloadButton() {
        JButton reloadButton = new JButton("reload");
        reloadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                loadPersonDataIntoFields();
            }
        });
        return reloadButton;
    }

    private void loadPersonDataIntoFields() {
        forenameField.setText(logic.getForename());
        surnameField.setText(logic.getSurname());
        birthdayField.setText(logic.getBirthday());
        streetField.setText(logic.getStreet());
        postalCodeField.setText(logic.getPostalCode());
        townField.setText(logic.getTown());
        countryField.setText(logic.getCountry());
    }

    public void quit() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(false);
                frame.dispose();
            }
        });
    }

    public void show() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(true);
            }
        });
    }

}```

#2

Komplexere Beispiele

Für den komplexeren Fall zeige ich zwei Anwendungsbeispiele, um die unterschiedlichen Ansprüche an mögliche Lösungen darzustellen:

[ul]
[li] Ein komplexeres Beispiel mit JFrame.[/li] [li] Ein komplexeres Beispiel mit JDialog.[/li][/ul]

In beiden Fällen wird das obige, einfache Beispiel aufgetrennt in einen PersonPanel und die Umgebung des ganzen, so dass dieses einmal in einem größeren JFrame und einmal innerhalb eines JDialogs aufgerufen wird. In diesem Beispiel macht das vielleicht nicht so schrecklich viel Sinn, in dem Problemfall, zu dem ich hier Fragen stellen wollte, was der Grund für die Erstellung all dieser Beispielklassen war, macht das aber sehr wohl Sinn und wird auch verwendet.

[ot]In einem echten Projekt hätte ich den doppelten Code in den beiden Beispielen natürlich vermieden. Hier jedoch geht es gerade um die Unterschiede zwischen diesen.

Die Beispiele zeigen quasi die Entstehungsgeschichte des komplexeren Falles aus dem einfacheren.

So ist hier auch einfach aus der Bequemlichkeit, für die Vorstellung der Klassen auf eine weitere Hilfsklasse verzichten zu wollen, die Methode setNiceLayoutManager() mehrfach vorhanden.[/ot]

Zunächst betrachten wir den PersonPanel, der beiden Beispielen zugrundeliegt. Er besteht aus einer Logik- und einer Gui-Klasse:

Logik des PersonPanels

Die Logik des PersonPanels steckt in der Klasse complex.logic.PersonPanelLogic:


import javax.swing.JPanel;

import complex.ui.PersonPanelGui;

import data.Person;

public class PersonPanelLogic implements PersonPanelLogicForGui, PersonPanelLogicForExternal {

    private final PersonPanelGui gui;

    private Person person;

    public PersonPanelLogic(final Person person) {
        this.person = person;
        gui = new PersonPanelGui((PersonPanelLogicForGui) this);
    }

    @Override
    public JPanel getPersonPanel() {
        return gui.getPersonPanel();
    }

    @Override
    public Person getPerson() {
        return person;
    }

    @Override
    public void savePersonFromGui() {
        gui.savePersonFromGui();
    }

    @Override
    public String getForename() {
        return person.getForename();
    }

    @Override
    public String getSurname() {
        return person.getSurname();
    }

    @Override
    public String getBirthday() {
        return person.getBirthday();
    }

    @Override
    public String getStreet() {
        return person.getStreet();
    }

    @Override
    public String getPostalCode() {
        return person.getPostalCode();
    }

    @Override
    public String getTown() {
        return person.getTown();
    }

    @Override
    public String getCountry() {
        return person.getCountry();
    }

    @Override
    public void setForename(final String forename) {
        person = Person.createWithNewForename(person, forename);
    }

    @Override
    public void setSurname(final String surname) {
        person = Person.createWithNewSurname(person, surname);
    }

    @Override
    public void setBirthday(final String birthday) {
        person = Person.createWithNewBirthday(person, birthday);
    }

    @Override
    public void setStreet(final String street) {
        person = Person.createWithNewStreet(person, street);
    }

    @Override
    public void setPostalCode(final String postalCode) {
        person = Person.createWithNewPostalCode(person, postalCode);
    }

    @Override
    public void setTown(final String town) {
        person = Person.createWithNewTown(person, town);
    }

    @Override
    public void setCountry(final String country) {
        person = Person.createWithNewCountry(person, country);
    }

}```

Um klar die Methoden der internen und der externen Verwendung zuzuordnen, habe ich zwei Interfaces erstellt:

[ul]
    [li] PersonPanelLogicForGui[/li]    [li] PersonPanelLogicForExternal[/li][/ul]

[ot]Die Namen sind ziemlich sicher Murks. Eigentlich sollten Interfaces das allgemeinere und eine Klasse, die dieses implementiert, etwas spezielleres sein, die Namen sollten dies ausdrücken. Aber die lassen sich immer noch ändern.

Falls jemand hierzu auch eine gute Idee hat, nur her damit.[/ot]

Wenig überraschender Weise sehen diese so aus:

Interface `complex.PersonPanelLogicForExternal`:

```package complex.logic;

import javax.swing.JPanel;

import data.Person;

public interface PersonPanelLogicForExternal {

    JPanel getPersonPanel();

    Person getPerson();

    void savePersonFromGui();

}```

Interface `complex.PersonPanelLogicForGui`:

```package complex.logic;

public interface PersonPanelLogicForGui {

    String getForename();

    String getSurname();

    String getBirthday();

    String getStreet();

    String getPostalCode();

    String getTown();

    String getCountry();

    void setForename(String forename);

    void setSurname(String surname);

    void setBirthday(String birthday);

    void setStreet(String street);

    void setPostalCode(String postalCode);

    void setTown(String town);

    void setCountry(String country);

}```

Oberfläche des PersonPanels

Die grafische Oberfläche des PersonPanels steckt in der Klasse `complex.ui.PersonPanelGui`:

```package complex.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import complex.logic.PersonPanelLogicForGui;

public class PersonPanelGui {

    private static final int LABEL_WIDTH = 100;
    private static final int FIELD_WIDTH = 300;
    private static final int FIELD_HIGHT = 30;

    private final PersonPanelLogicForGui logic;

    private final JPanel personPanel;

    private final JTextField forenameField;
    private final JTextField surnameField;
    private final JTextField birthdayField;
    private final JTextField streetField;
    private final JTextField postalCodeField;
    private final JTextField townField;
    private final JTextField countryField;

    public PersonPanelGui(final PersonPanelLogicForGui logic) {
        this.logic = logic;

        personPanel = new JPanel();
        forenameField = new JTextField();
        surnameField = new JTextField();
        birthdayField = new JTextField();
        streetField = new JTextField();
        postalCodeField = new JTextField();
        townField = new JTextField();
        countryField = new JTextField();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
                loadPersonDataIntoFields();
            }
        });
    }

    private void createGui() {
        personPanel.setLayout(new BorderLayout());

        personPanel.add(createDataButtons(), BorderLayout.EAST);
        personPanel.add(createDataArea(), BorderLayout.CENTER);
    }

    private Component createDataArea() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(0, 1, 5, 5));
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createFieldPanel("Nachname", forenameField));
        panel.add(createFieldPanel("Vorname", surnameField));
        panel.add(createFieldPanel("Geburtsdatum", birthdayField));
        panel.add(createFieldPanel("Straße", streetField));
        panel.add(createFieldPanel("Postleitzahl", postalCodeField));
        panel.add(createFieldPanel("Ort", townField));
        panel.add(createFieldPanel("Land", countryField));

        return panel;
    }

    private Component createFieldPanel(String labelText, JTextField field) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JLabel label = new JLabel(labelText + ":");

        label.setPreferredSize(new Dimension(LABEL_WIDTH, FIELD_HIGHT));
        field.setPreferredSize(new Dimension(FIELD_WIDTH, FIELD_HIGHT));

        panel.add(label, BorderLayout.WEST);
        panel.add(field, BorderLayout.CENTER);

        return panel;
    }

    private Component createDataButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        JPanel subpanel = new JPanel();
        subpanel.setLayout(new BorderLayout());

        subpanel.add(createClearAllButton(), BorderLayout.NORTH);
        subpanel.add(createReloadButton(), BorderLayout.SOUTH);
        panel.add(subpanel, BorderLayout.NORTH);

        panel.add(createSaveButton(), BorderLayout.SOUTH);

        return panel;
    }

    private Component createClearAllButton() {
        JButton clearAllButton = new JButton("clear");
        clearAllButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                forenameField.setText("");
                surnameField.setText("");
                birthdayField.setText("");
                streetField.setText("");
                postalCodeField.setText("");
                townField.setText("");
                countryField.setText("");
            }
        });
        return clearAllButton;
    }

    private Component createReloadButton() {
        JButton reloadButton = new JButton("reload");
        reloadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                loadPersonDataIntoFields();
            }
        });
        return reloadButton;
    }

    private void loadPersonDataIntoFields() {
        forenameField.setText(logic.getForename());
        surnameField.setText(logic.getSurname());
        birthdayField.setText(logic.getBirthday());
        streetField.setText(logic.getStreet());
        postalCodeField.setText(logic.getPostalCode());
        townField.setText(logic.getTown());
        countryField.setText(logic.getCountry());
    }

    private Component createSaveButton() {
        JButton okButton = new JButton("Save");
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changePersonData();
            }
        });
        return okButton;
    }

    private void changePersonData() {
        logic.setForename(forenameField.getText());
        logic.setSurname(surnameField.getText());
        logic.setBirthday(birthdayField.getText());
        logic.setStreet(streetField.getText());
        logic.setPostalCode(postalCodeField.getText());
        logic.setTown(townField.getText());
        logic.setCountry(countryField.getText());
    }

    public void savePersonFromGui() {
        changePersonData();
    }

    public JPanel getPersonPanel() {
        return personPanel;
    }

}```


Komplexeres Beispiel mit JFrame



Hier wird der PersonPanel innerhalb eines größeren Frames als ein Element eingebettet.

Start des komplexeren Beispiels mit JFrame

Den Startpunkt des komplexeren Beispielprogramms mit `JFrame` bildet die Klasse `complex.ComplexLogicAndGuiExampleWithFrame`:

```package start;

import complex.logic.PersonFrameLogic;

import data.Person;

public class ComplexLogicAndGuiExampleWithFrame {

    public static void main(String[] args) {
        Person person = new Person("Max", "Mustermann", "01.01.1990",
                "Musterstraße 123", "12345", "Musterhausen", "Deutschland");

        System.out.println("Zu Beginn:
    " + person);

        PersonFrameLogic logic = new PersonFrameLogic(person);
        logic.start();
    }

}```

Logik des komplexeren Beispiels mit JFrame

Die Logik des komplexeren Beispielprogramms mit `JFrame` bildet die Klasse `complex.logic.PersonFrameLogic`:

```package complex.logic;

import javax.swing.JPanel;

import complex.ui.PersonFrameGui;

import data.Person;

public class PersonFrameLogic {

    private final PersonPanelLogicForExternal personLogic;
    private final PersonFrameGui gui;

    public PersonFrameLogic(Person person) {
        personLogic = new PersonPanelLogic(person);
        gui = new PersonFrameGui(this);
    }

    public void start() {
        gui.show();
    }

    public JPanel getPersonPanel() {
        return personLogic.getPersonPanel();
    }

    public void okPressed() {
        personLogic.savePersonFromGui();
        System.out.println("Nach Klick auf Ok:
    " + personLogic.getPerson());
        quit();
    }

    public void quitPressed() {
        System.out.println("Nach Klick auf Quit:
    " + personLogic.getPerson());
        quit();
    }

    private void quit() {
        gui.quit();
    }

}```

Oberfläche des komplexeren Beispiels mit JFrame

Die grafische Oberfläche des komplexeren Beispielprogramms mit `JFrame` bildet die Klasse `complex.ui.PersonFrameGui`:

```package complex.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import complex.logic.PersonFrameLogic;

public class PersonFrameGui {

    private final PersonFrameLogic logic;

    private final JFrame frame;

    public PersonFrameGui(final PersonFrameLogic logic) {
        this.logic = logic;

        setNiceLayoutManager();

        frame = new JFrame();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
            }
        });
    }

    private static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createGui() {
        frame.setTitle("Komplexes Beispiel für Logik und Gui mit JFrame");
        frame.setLayout(new BorderLayout());

        frame.add(createAllButton(), BorderLayout.SOUTH);
        frame.add(createCenterPart(), BorderLayout.CENTER);

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private Component createCenterPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(3, 3, 5, 5));
        //panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createLabel());
        panel.add(createLabel());
        panel.add(createLabel());

        panel.add(createLabel());
        panel.add(createPersonPart());
        panel.add(createLabel());

        panel.add(createLabel());
        panel.add(createLabel());
        panel.add(createLabel());

        return panel;
    }

    private Component createLabel() {
        JLabel label = new JLabel("<html>Hier ist nichts<br>zu sehen, bitte "
                + "gehen<br>Sie weiter!<html>");
        label.setBorder(BorderFactory.createTitledBorder(""));
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    }

    private Component createPersonPart() {
        JPanel personPanel = logic.getPersonPanel();
        personPanel.setBorder(BorderFactory.createTitledBorder(""));
        return personPanel;
    }

    private Component createAllButton() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createQuitButton(), BorderLayout.WEST);
        panel.add(createOkButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createOkButton() {
        JButton okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.okPressed();
            }
        });
        return okButton;
    }

    private Component createQuitButton() {
        JButton quitButton = new JButton("Quit");
        quitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.quitPressed();
            }
        });
        return quitButton;
    }


    public void quit() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(false);
                frame.dispose();
            }
        });
    }

    public void show() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(true);
            }
        });
    }

}```


Komplexeres Beispiel mit JDialog

 

Hier wird der PersonPanel innerhalb eines Dialoges eingebettet, der in einer Oberfläche auf Knopfdruck gestartet wird.

Start des komplexeren Beispiels mit JDialog

Der Start des komplexeren Beispiels mit `JDialog` steckt in der Klasse `start.ComplexLogicAndGuiExampleWithDialog`:

```package start;

import complex.ui.AProgramWithADialogLogic;

public class ComplexLogicAndGuiExampleWithDialog {

    public static void main(String[] args) {
        AProgramWithADialogLogic logic = new AProgramWithADialogLogic();
        logic.start();
    }

}```

Logik des komplexeren Beispiels mit JDialog

Die Logik des Rahmenprogramms des komplexeren Beispiels mit `JDialog` steckt in der Klasse `complex.logic.AProgramWithADialogLogic`:

```package complex.logic;

import complex.ui.AProgramWithADialogGui;

public class AProgramWithADialogLogic {

    private final AProgramWithADialogGui gui;

    public AProgramWithADialogLogic() {
        gui = new AProgramWithADialogGui();
    }

    public void start() {
        gui.show();
    }

}```

Die Logik des Dialoges des komplexeren Beispiels mit `JDialog` steckt in der Klasse `complex.logic.PersonDialogLogic`:

```package complex.logic;

import java.awt.Point;

import javax.swing.JPanel;

import complex.ui.PersonDialogGui;
import data.Person;

public class PersonDialogLogic {

    private final PersonPanelLogicForExternal personLogic;
    private final PersonDialogGui gui;

    public PersonDialogLogic(final Point parentPosition) {
        personLogic = new PersonPanelLogic(initPerson());
        gui = new PersonDialogGui(this, parentPosition);
    }

    private static Person initPerson() {
        Person person = new Person("Max", "Mustermann", "01.01.1990",
                "Musterstraße 123", "12345", "Musterhausen", "Deutschland");
        System.out.println("Zu Beginn:
    " + person);
        return person;
    }

    public void start() {
        gui.show();
    }

    public JPanel getPersonPanel() {
        return personLogic.getPersonPanel();
    }

    public void okPressed() {
        savePersonFromGui();
        System.out.println("Nach Klick auf Ok:
    " + personLogic.getPerson());
        quit();
    }

    public void savePersonFromGui() {
        personLogic.savePersonFromGui();
    }

    public void quitPressed() {
        System.out.println("Nach Klick auf Quit:
    " + personLogic.getPerson());
        quit();
    }

    private void quit() {
        gui.quit();
    }

}```


Oberfläche des komplexeren Beispiels mit JDialog

Die grafische Oberfläche des Rahmenprogramms des komplexeren Beispiels mit `JDialog` steckt in der Klasse `complex.ui.AProgramWithADialogGui`:

```package complex.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import complex.logic.PersonDialogLogic;

public class AProgramWithADialogGui {

    private final JFrame frame;

    public AProgramWithADialogGui() {
        setNiceLayoutManager();

        frame = new JFrame();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
            }
        });
    }

    private static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createGui() {
        frame.setTitle("Komplexes Beispiel für Logik und Gui mit JDialog");
        frame.setLayout(new BorderLayout());

        // TODO : Frame mit Button, der wiederum Dialog startet...

        frame.add(createCenterPart(), BorderLayout.CENTER);

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private Component createCenterPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(3, 3, 5, 5));
        //panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createLabel());
        panel.add(createLabel());
        panel.add(createLabel());

        panel.add(createLabel());
        panel.add(createShowDialogButton());
        panel.add(createLabel());

        panel.add(createLabel());
        panel.add(createLabel());
        panel.add(createLabel());

        return panel;
    }

    private Component createLabel() {
        JLabel label = new JLabel("<html>Hier ist nichts<br>zu sehen, bitte "
                + "gehen<br>Sie weiter!<html>");
        label.setBorder(BorderFactory.createTitledBorder(""));
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    }

    private Component createShowDialogButton() {
        JButton dialogButon = new JButton("Bearbeite Personendaten");
        dialogButon.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Point parentPosition = frame.getLocation();
                PersonDialogLogic personLogic = new PersonDialogLogic(parentPosition);
                personLogic.start();
            }
        });

        return dialogButon;
    }

    public void show() {
        frame.setVisible(true);
    }

}```

Die grafische Oberfläche des Dialogs des komplexeren Beispiels mit `JDialog` steckt in der Klasse `complex.ui.PersonDialogGui`:

```package complex.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import complex.logic.PersonDialogLogic;

public class PersonDialogGui {

    private final PersonDialogLogic logic;

    private final JDialog dialog;

    public PersonDialogGui(final PersonDialogLogic logic,
            final Point parentPosition) {
        this.logic = logic;

        dialog = new JDialog();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui(parentPosition);
            }
        });
    }

    private void createGui(final Point parentPosition) {
        dialog.setTitle("Komplexes Beispiel für Logik und Gui mit JDialog - der Dialog");
        dialog.setLayout(new BorderLayout());

        dialog.add(createAllButton(), BorderLayout.SOUTH);
        dialog.add(createPersonPart(), BorderLayout.CENTER);

        dialog.pack();
        dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

        int parentX = (int) parentPosition.getX();
        int parentY = (int) parentPosition.getY();
        dialog.setLocation(new Point(parentX + 200, parentY + 120));
    }

    private Component createAllButton() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createQuitButton(), BorderLayout.WEST);
        panel.add(createGoodButtons(), BorderLayout.EAST);

        return panel;
    }

    private Component createGoodButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createApplyButton(), BorderLayout.WEST);
        panel.add(createOkButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createApplyButton() {
        JButton applyButton = new JButton("Apply");
        applyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changePersonData();
            }
        });
        return applyButton;
    }

    private Component createOkButton() {
        JButton okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                changePersonData();
                logic.okPressed();
            }
        });
        return okButton;
    }

    private void changePersonData() {
        logic.savePersonFromGui();
    }

    private Component createQuitButton() {
        JButton quitButton = new JButton("Quit");
        quitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logic.quitPressed();
            }
        });
        return quitButton;
    }

    private Component createPersonPart() {
        JPanel personPanel = logic.getPersonPanel();
        personPanel.setBorder(BorderFactory.createTitledBorder(""));
        return personPanel;
    }

    public void quit() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                dialog.setVisible(false);
                dialog.dispose();
            }
        });
    }

    public void show() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                dialog.setVisible(true);
            }
        });
    }

}```


Fragestellung

Neben dem grundsätzlichen Problem, ob die Logik die wichtigere ist oder die grafische Oberfläche, also welche zuerst erzeugt werden sollte und dann das Gegenstück erzeugt, stellt sich im wesentlichen das folgende Problem:

**Wie kann ich hier noch Logik und Gui sauber trennen?

Reicht die gezeigte Trennung vielleicht völlig aus?**

Mich stört, dass die `PersonLogic` das Panel mit den angezeigten Personendaten an die anderen Logiken weiterreichen muss, damit diese es wiederum ihrer Gui übergeben. Irgendwie ist mir da zu viel Gui-Interna in den Logikklassen. Oder stelle ich mich da nur an, um es salopp auszudrücken?

Konkret stört mich soetwas wie `import javax.swing.JPanel;` in einer Logikklasse.


Ferner lässt sich vielleicht das Zusammenspiel der verschiedenen Logiken und Gui-Klassen noch verbessern, gefühlsmäßig ist es holzig. In der Regel ein Indiz, dass es besser geht, eventuell gar völlig anders.

[ot]Die erste Frage habe ich für mich so beantwortet, dass die Logik die wichtigere Klasse ist, die von außen aufgerufen wird und dann die grafische Oberfläche erzeugt.[/ot]

Die Kopplung am Beispiel des Falles mit Dialog genauer betrachtet

In `complex.ui.PersonDialogGui` in der Methode `createPersonPart()`:

```    private Component createPersonPart() {
        JPanel personPanel = logic.getPersonPanel();
        personPanel.setBorder(BorderFactory.createTitledBorder(""));
        return personPanel;
    }```

In `complex.logic.PersonDialogLogic` in der Methode `getPersonPanel()`:

```    public JPanel getPersonPanel() {
        return personLogic.getPersonPanel();
    }```
    
In `complex.logic.PersonPanelLogic` in der Methode `getPersonPanel()`:

```    public JPanel getPersonPanel() {
        return gui.getPersonPanel();
    }```
    
In `complex.ui.PersonPanelGui` in der Methode `getPersonPanel()`:

```    public JPanel getPersonPanel() {
        return personPanel;
    }```


Lösungsansätze

[ul]
    [li] `logic.getGui().getPersonPanel()` Dann wiederum muss die äußere Gui die innere kennen, was eigentlich überhaupt nicht Not tut, weil es völlig ausreicht, nur das eine Panel zu kennen, das eingebettet werden soll.[/li]    [li] Mit der Verwendung eines Interfaces, das die Gui versteckt, etwa `PersonPanelGetter` kann dem abgeholfen werden. Dann hätten wir: `logic.getPanelGetter().getPanel()` und jede Ebene muss nicht mehr kennen, als sie es bisher tut (kein Import eines `JPanel` in einer Logikklasse), die Interna werden nicht nach außen enthüllt und dennoch kommt die Klasse, die drankommen muss, von außen an das Panel zur Person.[/li][/ul]




[ot]Ich hoffe, ich habe den Sachverhalt umfassend und klar dargestellt. Es hat ziemlich viel Zeit gekostet, aber schon dabei ist mir allerhand klar geworden, was in einer so relativ einfachen Struktur handlicher zu verbessern ist, als an einem (noch) komplexeren, realen Programm.[/ot]

#3

in einem Programm mit Klassen wie PersonFrameLogic und PersonPanelLogic eine Variable personLogic zu nennen ist ungünstig ungenau

treibt es da noch auf die Spitze, es gibt weder Klasse noch Interface PersonLogic…

aber ok, PersonPanelLogic ist die zentrale ‘Logic’-Klasse, Panel sogar schon im Namen, abgesehen von dem aber nicht viel mit GUI am Hut,
Name PersonLogic wäre durchaus auch wieder angemessen,
dein Zitat vielleicht irgendwas von Freud :wink: , unterbewußt bessere Ansicht der Dinge

dramatisch ist die Kopplung nicht, irgendwer muss nunmal den Überblick haben,
wobei ich dir zustimme, dass die besser benannte PersonLogic besser vom Panel frei wäre,
sollte genauso mit Textprogramm, Webauftritt, AWT oder SWK, Eclipse-Plugin usw. funktionieren

aber die Frage bleibt, irgendwer muss alles zusammenführen, da ist ein Aufbau aus dem Stehgreif eher müßig,
innerhalb der GUI-Schicht oben drauf sollte es gewiss eine zentrale ‘ich weiß wo’s langgeht’-Komponente geben, die

  • alle benötigten Logiken kennt/ ggfs. erzeugt oder übergeben bekommt,
  • alle GUI-Bausteine kennt/ wahrscheinlich erzeugt, untereinander bekannt macht

ob man die dann nun Logic nennt oder was auch immer…, sei es die Logic der GUI-Ebene


#4

Stimmt, ich hatte die Klasse PersonLogic in PersonPanelLogic umbenannt, es bei den Variablen aber vergessen. Guter Tipp!

Ändern kann ich es oben nun nicht mehr, aber egal.


#5

Welche Aufgabe soll denn die Anwendung überhaupt übernehmen?
Was ist ein Anwendungsfall der abgedeckt werden soll?

Das ist das erste worüber man sich klar werden sollte.
Die Anwendung oder besser gesagt die Architektur sollte genau das wiederspiegeln.
Das waren keine rhetorischen Fragen.

Dependency Injection, Inversion of Control sind nun zwei wichtige Stichworte.

Im Gegensatz zu Swing hat SWT einen nicht zu vernachlässigenden Punkt besser gelöst.
Jede Komponente muss im Konstruktor ihren Parent erhalten auf dem es gezeichnet wird.
Damit vereinfacht sich das Problem, ob in einem JFrame einem JPanel oder auf einem JDialog gezeichnet, da auf einem “Renderer” lediglich ein anderer Parent übergeben wird.

JavaFX mit fxml ist auch empfehlenswert, da es eine sehr gute Aufteilung von Haus aus mitbringt.
Die View nur in fxml deklariert, kann programmatisch so gut wie nichts außer Darstellen und verweist für alles andere auf einen Controller.

Der Controller wird automatisch zu jeder View initialisiert und erhält Zugriff auf alle nach außen hin sichtbaren Komponenten und bietet Callbacks für alle Buttons.

Pseudocode

Controller controller = view.getController();
controller.setLogic(logic);
controller.setData(...);

Scene scene = new Scene(view);

stage.setScene(scene);
// oder
dialog.setScene(scene)
...


class Controller {
  
  @FXML
  TextField input;

  Logic logic;
  
  void setLogic(Logic logic) {
    this.logic = logic;
  }
  ...

  @FXML
  public void btnClick() {
    String txt = input.getText();
    logic.apply(txt)
  }  
}```

Der Controller und die Logik haben sogut wie nichts miteinander zu tun. Zwei verschiedene Komponenten, von denen der Controller an die Logik weiterleitet.

#6

Klingt gut. Ich habe Swing halt “im Blut”, aber ich hatte auch schon angedacht, ein Testprojekt mit Java-FX zu erstellen. Diese unterstützte Trennung ist nochmal ein Argument mehr.

Da werde ich hier im Forum aber auch nochmal einen Thread eröffnen, damit ich es dann auch gleich richtig mache und nicht vielleicht mit einer schlechten Anleitung beginne.


#7

Schreibe deinen Beitrag besser im Byte-Welt-Wiki, hier im Forum geht er irgendwann unter. Im Wiki ist eindeutig der bessere Ort.
Dort können mehrere Leute gemeinsam an einem Beitrag arbeiten.
Die Zugangsdaten sind die Gleichen, wie für das Forum.