Wzorzec MVC w javie

0

Witam

Miesiąc temu zacząłem naukę javy, chcę zaprojektować prosty edytor map do gry 2d i mam pewne pytania. Programować obiektowo potrafię mvc wyniosłem z php ale nie wiem jak ma to wyglądać w javie. Chcę napisać możliwie najlepszej jakości kod (mając na uwadze moje niewielkie doświadczenie w tym języku).

Czy jeśli tworzę okno edytora, to ma ono być obiektem np. FrameEditor i przechowywać w sobie, FrameEditorController, FrameEditorView i FrameEditorModel?

W oknie głównym edytora mają znajdować się inne okienka/panele/przyciski/menu i dla każdego komponentu tworzyć taką strukturę klas? Jeśli tak to w jaki sposób połączyć klacy FrameEditor z klasą np. MainMenu?

W internecie znalazłem kilka przykładów ale są one proste ilustrują one programy które zawierają 1 widok 1 model i 1 kontroler i nie wiem jak ma to wyglądać.

1

Spring IoC

0

Dzięki, frameworka nie znam zaraz będę czytał. Jeśli ktoś ma jakieś inne sugestie także będę wdzięczny.

0

Tak czytam o IoC i raczej nie o to mi chodziło. Robię klasę menu EditorMenubarController dziedziczącą po JMenuBar ma ona w sobie EditorMenuBarModel i EditorMenuBarView. Teraz do menu chcę dodać podmenu i tu pytanie jak to zrobić tj. w którym miejscu aplikacji tworzyć to podmenu czy to ma być w kontrolerze EditorMenubarController?

0

Moim zdaniem powinieneś zainteresować się wzorcem Model-View-Presenter.

Na Twoim przykładzie Map byłaby klasą modelu. Umożliwiałaby zarejestrowania słuchacza zmian (np. wzorzec Observer). Częścią interfejsu takiego słuchacza mogłaby być np. metoda:

void registerChangeListener(IMapChangeListener listener);

Prezenter zarejestrowałby się jako słuchacza zmiany w mapie. Miałby też dostęp do interfejsu widoku, np.

interface IView
{
  void displayMap(Map map);
...
}

Za tworzenie kontrolek, menu itp odpowiadałaby konkretna implementacja danego IView. Prezenter oraz Model nie muszą nic wiedzieć na temat konkretnej implementacji Widoku! Nie wiedzą nawet, czy jest to GUI czy konsola, czy ma menu i przyciski itp.!

Dla danego Widoku tworzysz sobie jeden Prezenter. Wszystkie zdarzenia inicjowane przez użytkownika idą przez Prezentera, który wykonuje operacje na Modelu.

0

@cannabis IoC jest przydatne w każdym większym programie ;)

0

Czytałem o nim i na pewno się przyda, brakowało mi tego w php, nie wiedziałem jak to się nazywa :)

Dzięki mychal o to właśnie mi chodziło, spróbuję jakiś prosty kod napisać i wrzucę dla pewności czy to dobrze rozumiem.

0

Piszę sobie kod na próbę i napotkałem pewien problem którego nie rozumiem a nie wiem jak google o to zapytać.

Mam takie klasy:

public abstract class BModel extends Observable {

}



public class MainFrameModel extends BModel {
	
	private int counter;
	
	public void setValue(int value) {
		this.counter = value;
		setChanged();
		notifyObservers(counter);
	}
	
	public void increaseValue() {
		++counter;
		setChanged();
		notifyObservers(counter);
	}
	
}



public abstract class BController implements ActionListener {

	protected BModel model;
	protected BView view;

	public void setModel(BModel model) {
		this.model = model;
	}
	
	public BModel getModel() {
		return this.model;
	}
	
	public void setView(BView view) {
		this.view = view;
	}
	
	public BView getView() {
		return this.view;
	}
}



public class MainFrameController extends BController {

        //błąd
	public MainFrameController() {
		this.model.increaseValue();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		
	}
	
	public void addModel(MainFrameModel m){
		this.model = m;
	}

	public void addView(BView v){
		this.view = v;
	}

        //błąd
	public void initModel(int x){
		model.setValue(x);
	}
	
}

i w klasie MainFrameController w liniach z komentarzem błąd jest błąd. Nie wiem jak to rozwiązać bo obiekt BModel (model bazowy dla wszystkich modeli), nie posiada tych metod a dziedziczący MainFrameController już je ma. Do kontrolera MainFrameController ładuję model MainFrameModel nie BModel.

3

Ehh, mam wrażenie, że próbujesz to zrobić jakoś tak na siłę. Po co Ci ten abstakcyjny model? Przygotowałem Ci gotowy przykład z opisem. Jest to okienkowa aplikacja ze stoperem :P. W załączniku projekt IntelliJ.

Klasa Main - nic szczególnego, ale żeby było kompletnie:

public class Main {
    public static void main(String[] args) {
        MainFrame mainFrame = new MainFrame();
        mainFrame.setVisible(true);
    }
}

Interfejsy - ICounterChangeListener oraz ICounterView.

public interface ICounterChangeListener {
    void onCounterChanged();
}
public interface ICounterView {
    void displayCounterValue(int counterValue);
}

Zaczyna się robić ciekawie - klasa modelu - Counter. Daje możliwosć zwiększania wartości co 1 sekundę. O zmianie wartości informuje swoich słuchaczy.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class Counter {
    private int value;
    private List<ICounterChangeListener> changeListeners = new ArrayList<>();
    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    private ScheduledFuture currentTimer;

    public Counter() {
    }

    private void notifyChangeListeners() {
        for (ICounterChangeListener listener : changeListeners) {
            listener.onCounterChanged();
        }
    }

    private void increaseValue() {
        value++;
        notifyChangeListeners();
    }

    public int getValue() {
        return value;
    }

    public void start() {
        value = 0;
        if (currentTimer == null) {
            currentTimer = executor.scheduleAtFixedRate(() -> increaseValue(), 0, 1, TimeUnit.SECONDS);
        }
    }

    public void addChangeListener(ICounterChangeListener listener) {
        changeListeners.add(listener);
    }

    public void removeChangeListener(ICounterChangeListener listener) {
        changeListeners.remove(listener);
    }

    public void stop() {
        if (currentTimer != null) {
            currentTimer.cancel(true);
            currentTimer = null;
            value = 0;
            notifyChangeListeners();
        }
    }
}

Klasa Prezentera, reaguje na zdarzenia GUI oraz na zmiany Modelu. Komunikuje zmiany Modelu Widokowi (poprzez ICounterView). Prezenter (podobnie jak Kontroler) powinien być możliwie chudziutki i delegować zadania.

public class MainFramePresenter implements ICounterChangeListener {
    private final ICounterView counterView;
    private Counter counter = new Counter();

    public MainFramePresenter(ICounterView counterView) {
        this.counterView = counterView;
        this.counter.addChangeListener(this);
    }

    @Override
    public void onCounterChanged() {
        this.counterView.displayCounterValue(counter.getValue());
    }

    public void startCounter() {
        this.counter.start();
    }

    public void stopCounter() {
        this.counter.stop();
    }
}

Na koniec implementacja widoku (zrobiona raczej od niechcenia, byle było :P)

import javax.swing.*;
public class MainFrame extends JFrame implements ICounterView {
    private JLabel lCounterValue;
    private MainFramePresenter presenter;

    public MainFrame() {
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        initializeGUI();
        presenter = new MainFramePresenter(this);
    }

    private void initializeGUI() {
        setTitle("Counter");
        setResizable(false);
        setSize(250, 100);
        lCounterValue = new JLabel();

        JPanel labelPanel = new JPanel();
        labelPanel.setSize(100, 15);
        labelPanel.setLocation(10, 10);
        labelPanel.add(lCounterValue);

        JButton btnStartCounter = new JButton("Start");
        btnStartCounter.setSize(100, 15);
        btnStartCounter.setLocation(10, 40);
        btnStartCounter.addActionListener((evt) -> presenter.startCounter());

        JButton btnStopCounter = new JButton("Stop");
        btnStopCounter.setSize(100, 15);
        btnStopCounter.setLocation(120, 40);
        btnStopCounter.addActionListener((evt) -> presenter.stopCounter());

        getContentPane().add(btnStopCounter);
        getContentPane().add(btnStartCounter);
        getContentPane().add(labelPanel);
    }

    @Override
    public void displayCounterValue(final int counterValue) {
        SwingUtilities.invokeLater(() -> lCounterValue.setText(String.valueOf(counterValue)));
    }
}
0

Ojej dzięki, że ci się chciało to zrobić :)

Tej konstrukcji nie rozumiem i nie wiem jak to poprawić bo eclipse mi błędy wyświetla (Syntax Error) fragment klasy Counter:

currentTimer = executor.scheduleAtFixedRate(() -> increaseValue(), 0, 1, TimeUnit.SECONDS);

oraz w MainFrame:

btnStartCounter.addActionListener((evt) -> presenter.startCounter());
...
btnStopCounter.addActionListener((evt) -> presenter.stopCounter());
...
SwingUtilities.invokeLater(() -> lCounterValue.setText(String.valueOf(counterValue)));
0

To są lambdy (dostępne od Javy w wersji 8). Pewnie musisz zainstalować nowe JDK. Możesz też je zastąpić implementacją poprzez klasę anonimową.

0

Mam windows XP, nie działa na nim java 8 https://www.java.com/pl/download/faq/winxp.xml .

Właściwość prezenter z klasy MainFrame zrobiłem publiczną i zastąpiłem ją:

        btnStopCounter.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				presenter.stopCounter();
			}
        	
        });

Reszte przerobiłem na anonimowe teraz czas na analizowanie, dzięki i jeszcze na pewno się tu odezwę z pytaniami odnośnie kodu

0

Pozwólcie, że odkopię swój temat (taki stary to on znowu nie jest).

Siedzę dalej przy mvc/mvp i nasuwają mi się kolejne pytania jeśli chodzi o język to go łykam bez problemu gorzej z wzorcami projektowymi :/

Tworzę sobie ten edytor (edytor = klasa). Tworzę sobie teraz główne okno w oparciu o mvc i mam trzy klasy MainFrameController, MainFrameView i MainFrameModel. W MainFrameView buduję cały interfejs a w MainFrameModel znajdują się dane całego projektu edytora. Teraz trochę się gubię. Główne okno edytora jest podzielone na 4 osobne panele:

  • panel projektu
  • panel konsoli
  • panel głównego okna mapy
  • panel dodawania obiektów

I teraz pytanie czy dla każdego panelu tworzyć osobny mvc czy tworzyć je wszystkie w jednym miejscu tj. MainFrameView. W zasadzie nie wiem jak dobrze zadać to pytanie.

0

A może być kod tutaj?

Tak wygląda tworzenie głównego okna edytora:

package editor.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

import editor.controller.EditorController;

public class EditorView extends JFrame {
	
	private EditorController controller;
	
	private JMenuBar mainMenu;
	
	private JMenu fileMenu;
	private JMenu newSubMenu;
	
	private JMenu viewMenu;
	
	private JMenuItem closeItem;
	private JMenuItem newProjectItem;
	private JMenuItem newSystemItem;
	private JMenuItem newPlanetItem;
	private JMenuItem newStarItem;
	private JMenuItem saveItem;
	private JMenuItem saveAsItem;
	private JMenuItem openItem;
	
	private JCheckBoxMenuItem displayGridItem;
	
	private JLabel statusLabel;
	
	private Container framePane;
	
	private JSplitPane mainContainer;
	private JSplitPane rightContainer;
	private JSplitPane topContainer;
	
	private JPanel mapStructureContainer;
	private JPanel mapObjectsContainer;
	private JPanel mapContainer;
	private JPanel consoleContainer;
	
	public EditorView(EditorController controller) {
		this.controller = controller;
		
		this.mainMenu = this.initFrameMainMenu();
		this.setJMenuBar(this.mainMenu);
		
		this.framePane = this.initFrameContentContainer();
		this.setContentPane(this.framePane);
		
		this.setTitle("Galaxy Editor");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setSize(new Dimension(800, 600));
		this.setVisible(true);
	}
	
	private JMenuBar initFrameMainMenu() {
		this.closeItem = new JMenuItem("Close");
		this.closeItem.setActionCommand("close");
		this.closeItem.addActionListener(this.controller);
				
		this.newProjectItem = new JMenuItem("Project");
		
		this.newSystemItem = new JMenuItem("System");
		this.newSystemItem.setEnabled(false);
		
		this.newStarItem = new JMenuItem("Star");
		this.newStarItem.setEnabled(false);
		
		this.newPlanetItem = new JMenuItem("Planet");
		this.newPlanetItem.setEnabled(false);
		
		this.saveItem = new JMenuItem("Save");
		this.saveItem.setEnabled(false);
		
		this.saveAsItem = new JMenuItem("Save ass...");
		this.saveAsItem.setEnabled(false);
		
		this.openItem = new JMenuItem("Open");
		
		this.displayGridItem = new JCheckBoxMenuItem("Display grid");
		
		this.newSubMenu = new JMenu("New");
		this.newSubMenu.add(newProjectItem);
		this.newSubMenu.addSeparator();
		this.newSubMenu.add(newSystemItem);
		this.newSubMenu.add(newStarItem);
		this.newSubMenu.add(newPlanetItem);
		
		this.viewMenu = new JMenu("View");
		this.viewMenu.add(displayGridItem);
		
		this.fileMenu = new JMenu("File");
		this.fileMenu.add(newSubMenu);
		this.fileMenu.add(openItem);
		this.fileMenu.addSeparator();
		this.fileMenu.add(saveItem);
		this.fileMenu.add(saveAsItem);
		this.fileMenu.addSeparator();
		this.fileMenu.add(closeItem);
		
		JMenuBar mainMenu = new JMenuBar();
		mainMenu.add(fileMenu);
		mainMenu.add(viewMenu);
		
		return mainMenu;
	}
	
	private Container initFrameContentContainer() {
		Container framePane = this.getContentPane();
		framePane.setLayout(new BorderLayout());
		
		this.statusLabel = this.initStatusContainer();
		framePane.add(this.statusLabel, BorderLayout.PAGE_END);
		
		this.mainContainer = this.initMainContainer();
		framePane.add(this.mainContainer, BorderLayout.CENTER);
		
		return framePane;
	}
	
	private JLabel initStatusContainer() {
		JLabel statusLabel = new JLabel("Status");
		
		return statusLabel;
	}
	
	private JSplitPane initMainContainer() {
		this.mapStructureContainer = this.initMapStructureContainer();
		this.rightContainer = this.initRightContainer();
		
		JSplitPane mainContainer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
				this.mapStructureContainer,
				this.rightContainer);
		mainContainer.setDividerLocation(200);		
		
		return mainContainer;
	}
	
	private JPanel initMapStructureContainer() {
		JPanel panel = new JPanel();
		panel.setBackground(Color.white);

		return panel;
	}
	
	private JSplitPane initRightContainer() {
		this.topContainer = this.initTopContainer();
		this.consoleContainer = this.initConsoleContainer();
		
		JSplitPane rightContainer = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
				this.topContainer,
				this.consoleContainer);
		rightContainer.setDividerLocation(400);
		
		return rightContainer;
	}
	
	private JSplitPane initTopContainer() {
		this.mapContainer = this.initMapContainer();
		this.mapObjectsContainer = this.initMapObjectsContainer();
		
		JSplitPane topContainer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
				this.mapContainer,
				this.mapObjectsContainer);
		topContainer.setDividerLocation(400);
		
		return topContainer;
	}
	
	private JPanel initConsoleContainer() {
		JPanel consoleContainer = new JPanel();
		
		return consoleContainer;
	}
	
	private JPanel initMapContainer() {
		JPanel mapContainer = new JPanel();
		
		return mapContainer;
	}
	
	private JPanel initMapObjectsContainer() {
		JPanel mapObjectsContainer = new JPanel();
		
		return mapObjectsContainer;
	}
	
}

A tutaj kod kontrolera:

package editor.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import editor.view.EditorView;

public class EditorController implements ActionListener {

	private EditorView view;
	
	public EditorController() {

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		switch (e.getActionCommand()) {
			case "close":
				this.closeAction();
				break;
		}
	}

	private void closeAction() {
		System.exit(0);
	}

	
	
	public static void main(String[] args) {
		EditorController controller = new EditorController();
		EditorView view = new EditorView(controller);
	}
}
 

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