Poprawna architektura prostej (i nie tylko) aplikacji. Jaka powinna byc?

0

Cześc

swoją przygodę z Javą dopiero rozpoczynam. Nurtuje mnie jedno pytanie: jak powinna wyglądac poprawna architektura aplikacji w Javie bez użycia jakiegokolwiek frameworka. Żeby wyjaśnic dokładniej o co mi chodzi przyjmę, że piszą prostą aplikację, która ma zapisac dane do pliku oraz może pobrac dane z pliku.

Rozumiem, że zaczynam od utworzenia klasy głównej z metodą main(), która wystartuje progam. I teraz pytanie zasadnicze: gdzie umieszczac sterowanie aplikacją (coś ala kontrolery z podejścia MVC), tak, żeby nie zrobic śmietnika w metodzie main().

Czyli gdzie umieszczac akcje pt. otwórz plik (które podłączę do jakiegoś obiektu z interface'u), zapisz plik? Przyszedłem z programowania w PHP i o ile obiektowośc nie jest dla mnie żadnym problemem to zrozumienie sterowania aplikacją tak żeby była czytelna już tak.

Z góry dzięki za pomoc.

1

Ale przecież to o co pytasz to właśnie jest OOP!
Lekcja na dziś: zasada jednej odpowiedzialności. Wynika z niej że każda taka osobna funkcjonalność powinna być reprezentowana przez osobną klasę.

0
Shalom napisał(a)

Ale przecież to o co pytasz to właśnie jest OOP!
Lekcja na dziś: zasada jednej odpowiedzialności. Wynika z niej że każda taka osobna funkcjonalność powinna być reprezentowana przez osobną klasę.

Dzięki Shalom! Co do zasady to odpowiedzialności to rozumiem. Bardziej chodziło mi o poprawne wykorzystane metody main(). Jak to robic, żeby ta metoda nie stała się "całym ciałem" programu ;-)? Czy np. jeśli w tym prostym programie, po uruchomieniu muszę stworzyc JFrame to buduję klasę np. Window a w niej metodę np. init() czyli Windows.init (ta metoda będzie odpowiedzialna za przygotowanie spraw okienkowych) i tę metodę wywołuję w metodzie main()?

I dalej, jeśli podczas uruchamiana programu chcę wczytac jakiś plik to robię to również dodając do metody main() wywołanie np. File.read(String name)? Czy może tę metodę powinienem załadowac do klasy np. Application.init() i tam wsadzic wszystko co będzie inicjalizowane podczas uruchomienia? Czy w Application.init() powinienem również umieścic Window.init()?

Chodzi mi o poprawne podejście.

1

I ty mówisz ze masz doświadczenie z OOP? o_O
Zasadniczo w aplikacji okienkowej w main() widzę miejsce tylko na:

  • stworzenie okienka które jest naszym MainWindow
  • ustawienie odpowiedniego lookAndFeel jeśli chcemy
    I tyle. Absolutnie nic więcej.
    Rozumiem że z pliku chcesz wczytać dane potrzebne do inicjalizacji okienka albo innego komponenetu? To robisz to na przykład w jego konstruktorze.
0
Shalom napisał(a)

I ty mówisz ze masz doświadczenie z OOP? o_O

Mam nawet kilkuletnie ;-) I podejrzewam, że mógłbym z Tobą pogadac jak równy z równym ;-) A może i nie? :-) W każdym razie jak jesteś z Poznania to z chęcią pogadałbym przy piwie, byłoby mi bardzo miło zdobyc swojego nauczyciela Java.

Shalom napisał(a)

Zasadniczo w aplikacji okienkowej w main() widzę miejsce tylko na:

  • stworzenie okienka które jest naszym MainWindow
  • ustawienie odpowiedniego lookAndFeel jeśli chcemy
    I tyle. Absolutnie nic więcej.
    Rozumiem że z pliku chcesz wczytać dane potrzebne do inicjalizacji okienka albo innego komponenetu? To robisz to na przykład w jego konstruktorze.

Ok, czy możesz mi wskazac błędy popełnione w poniższej prostej aplikacji, która posiada jakieś okienko, które można schowac do traya (a schowane co jakiś czas pojawia się, ale żeby nie komplikowac nie wklejałem tego tutaj)

Klasa główna:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplewords;

/**
 *
 */
public class SimpleWords {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws InterruptedException {
        Application.setup();
    }
}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplewords;

import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 *
 */
public class Application {
    
    public static void setup() {
        SysTray sysTray = new SysTray();        
        TrayIcon icon = new TrayIcon(Toolkit.getDefaultToolkit().createImage("c://generic_chat.png"),"Two clicks to SimpleWords!", sysTray.createPopupMenu());
        icon.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Window window = new Window();
                window.setup();
            }
        });

        try {
            SystemTray.getSystemTray().add(icon);            
        } catch (Exception e) {
        }        
    }
    
}
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplewords;

import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.PopupMenu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.plaf.metal.MetalIconFactory;

public class SysTray {

    public Image getImage() throws HeadlessException {

        Icon defaultIcon = MetalIconFactory.getTreeComputerIcon();

        Image img = new BufferedImage(defaultIcon.getIconWidth(),
                defaultIcon.getIconHeight(),
                BufferedImage.TYPE_4BYTE_ABGR);

        defaultIcon.paintIcon(new Panel(), img.getGraphics(), 0, 0);

        return img;

    }

    public PopupMenu createPopupMenu() throws
            HeadlessException {

        PopupMenu menu = new PopupMenu();

        MenuItem addNewWord = new MenuItem("Add phrase");
        MenuItem libraryManager = new MenuItem("Library manager");
        MenuItem settings = new MenuItem("Settings");
        MenuItem exit = new MenuItem("Exit");

        exit.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                System.exit(0);

            }
        });

        menu.add(addNewWord);
        menu.add(libraryManager);
        menu.add(settings);
        menu.add(exit);

        return menu;

    }

}

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplewords;
// window
import java.awt.Color;
import java.awt.Container;
import javax.swing.*;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.util.Timer;
/**
 *
 */
public class Window extends JFrame {

    /**
     * Setup window properties and display
     * @param disable_timer 
     */
    public void setup() {
        final JFrame frame = new JFrame("SimpleWords!");
        frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        frame.setSize(450, 200);
        //frame.setUndecorated(true);
        frame.setLayout(new GridLayout(1, 2));

        Color color = new Color(99, 207, 17);
        Container content = frame.getContentPane();
        content.setBackground(color);
        
        frame.setIconImage(Toolkit.getDefaultToolkit().createImage("c://generic_chat.png"));
        
        JLabel label = new JLabel(new ImageIcon("c://generic_chat_logo.png"));
        frame.add(label);
        
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension size = frame.getSize();
        screenSize.height = screenSize.height / 2;
        screenSize.width = screenSize.width / 2;
        size.height = size.height / 2;
        size.width = size.width / 2;
        int y = screenSize.height - size.height;
        int x = screenSize.width - size.width;

        frame.setBackground(Color.yellow);
        frame.setLocation(x, y);
        frame.setVisible(true);

        frame.addWindowStateListener(new WindowStateListener() {
            @Override
            public void windowStateChanged(WindowEvent e) {
                Timer timer = new Timer();
                timer.scheduleAtFixedRate(new FrameShowHideTask(frame), 6000, 6000);
            }
        });
    }
}

1

W zasadzie wygląda ok. Popup menu wyrzuciłbym do nowej klasy bo teraz jest małe, ale jakby się nagle zrobiło większe to może lepiej żeby było jednak osobno. Poza tym teraz jest uzależnione od SysTray, a może okaże się że wykorzystasz je też gdzieś indziej, np. jako menu kontekstowe?
Poza tym nie rozumiem po co rozszerzasz w Window JFrame skoro potem i tak tworzysz sobie w tej klasie JFrame do którego wszystko wrzucasz. Albo jedno albo drugie ;)
Action listenery tez wyrzucałbym do nowych klas, bo póki aplikacja jest mała wygląda to ok ale jak się okazuje że GUI się rozbuduje i masz tam milion guzików to te klasy anonimowe przestają być takie fajne.

Ale w takim razie dziwią mnie trochę twoje pytania, bo pytałeś czy masz wszystko wpychać do main() a z tego co widzę to nie masz z tym problemu i wiesz co tam być powinno :)

0
Shalom napisał(a)

W zasadzie wygląda ok. Popup menu wyrzuciłbym do nowej klasy bo teraz jest małe, ale jakby się nagle zrobiło większe to może lepiej żeby było jednak osobno. Poza tym teraz jest uzależnione od SysTray, a może okaże się że wykorzystasz je też gdzieś indziej, np. jako menu kontekstowe?
Poza tym nie rozumiem po co rozszerzasz w Window JFrame skoro potem i tak tworzysz sobie w tej klasie JFrame do którego wszystko wrzucasz. Albo jedno albo drugie ;)
Action listenery tez wyrzucałbym do nowych klas, bo póki aplikacja jest mała wygląda to ok ale jak się okazuje że GUI się rozbuduje i masz tam milion guzików to te klasy anonimowe przestają być takie fajne.

Bardzo Ci dziękuję za rady. Wrzucam do bookmarków w kategorii Edukacja :-)

Shalom napisał(a)

Ale w takim razie dziwią mnie trochę twoje pytania, bo pytałeś czy masz wszystko wpychać do main() a z tego co widzę to nie masz z tym problemu i wiesz co tam być powinno :)

Mimo wszystko wolę zaczerpnąc opinii od kogoś kto się zna (np. od Ciebie :-). Poza tym nawet nie wiesz jak mnie podniosłeś na duchu pisząc, że nie mam z tym problemu ;-)

Bardzo Ci dziękuję za klarowne wytłumaczenie tematu.

0

@Shalom

Z tymi Action listenerami, to docelowo sugerujesz chyba klasy wewnętrzne, tak? Czyli coś na zasadzie:

class MainFrame
	extends JFrame
	{
	public MainFrame()
		{
		JButton foo = new JButton();
		foo.addActionListener(new FooAction());
		}
	private class FooAction
		extends AbstractAction
		{
		@Override public void actionPerformed(ActionEvent event)
			{
			//do stuff
			}
		}
	}

Gdyż całkowite wyjście poza klasę okna oznacza utratę dostępu do pól prywatnych, co jednak jest przydatne w listenerach.

1

Jak najbardziej mówię tu o osobnych klasach. Czemu? A co jeśli masz w okienku guzik który wykonuje akcję X. I w menu masz opcję która też uruchamia tą samą akcję. I z menu kontekstowego tez można ją wywołać. Skopiujesz ten sam kod 3 razy? Poza tym jeśli masz np. 20 buttonów to robi ci się dodatkowe przynajmniej 200 linii kodu w jednym pliku ot tak.
Wcale nie tracisz dostępu bo powinieneś te pola przekazać do action listenera w konstruktorze
Nie wyświetla ramki
tu masz przykładowy kod ktory pokazuje że action listener w osobnej klasie wcale niczego nie ogranicza, a ułatwia przeglądanie kodu ;)

0

Klas wewnętrznych trzymałem się w sytuacji, gdy podpinana była tylko w jednym miejscu. W przytoczonym przez Ciebie przypadku wielokrotnego użycia faktycznie już przenosiłem klasę na zewnątrz i zazwyczaj w konstruktorze listenera przekazywałem tylko obiekt okna i rozbudowywałem jego klasę o kolejne gettery dla pól prywatnych, których listener potrzebował, co właśnie niezbyt mi odpowiadało. Przez to o ile zasadę jednej odpowiedzialności zazwyczaj staram się utrzymywać, to kod głównego okna, jako tego "od wszystkiego" zawsze zbyt mocno się rozrastał. Faktycznie dobra sugestia z tym konstruktorem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1