[[oktatas:programozás:java|< Java]] ====== Java MVC ====== * **Szerző:** Sallai András * Copyright (c) 2014, Sallai András * Szerkesztve: 2014, 2015, 2019, 2020 * Licenc: [[https://creativecommons.org/licenses/by-sa/4.0/|CC BY-SA 4.0]] * Web: https://szit.hu ===== Az MVC ===== A Model View Controller szavakból alkotott betűszó. A szoftverfejlesztésben használatos **szerkezeti minta**. Azt jelenti, szétválasztjuk a megjelenésérét felelős kódrészeket, az adatokért és a vezérlésért felelős kódrészektől. {{:oktatas:programozás:java:mvc_felepitese.png|}} * Model - üzleti logika, tárolás * View - megjelenés, Swing komponensek * Controller - vezérlés, eseménykezelés ===== Egyszerű MVC ===== A következő példában egyetlen állományban valósítjuk meg az MVC-t. A View név helyett a MainWindow nevet használom osztálynévnek. De lehetne akár View osztály is. import javax.swing.JFrame; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JTextField; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.FlowLayout; class MainWindow extends JFrame { private JButton calcButton = new JButton("Számít"); public JTextField baseField = new JTextField(10); public JTextField heightField = new JTextField(10); public JTextField resultField = new JTextField(10); private JLabel baseLabel = new JLabel("alap:"); private JLabel heightLabel = new JLabel("magasság:"); MainWindow() { resultField.setVisible(false); add(baseLabel); add(baseField); add(heightLabel); add(heightField); add(resultField); add(calcButton); setLayout(new FlowLayout()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); centerWindow(this); setVisible(true); } public void addCalcButtonActionListener(ActionListener listener) { calcButton.addActionListener(listener); } public static void centerWindow(java.awt.Window frame) { java.awt.Dimension dimension = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); int x = (int)((dimension.getWidth() - frame.getWidth()) / 2); int y = (int)((dimension.getHeight() - frame.getHeight()) / 2); frame.setLocation(x, y); } } class Controller { MainWindow mainWindow = new MainWindow(); Model model = new Model(); Controller() { mainWindow.addCalcButtonActionListener(new ActionListener(){ public void actionPerformed(ActionEvent event) { String baseStr = mainWindow.baseField.getText(); String heightStr = mainWindow.heightField.getText(); double base = Double.parseDouble(baseStr); double height = Double.parseDouble(heightStr); Double area = model.calcArea(base, height); mainWindow.resultField.setText(area.toString()); mainWindow.resultField.setVisible(true); mainWindow.pack(); } }); } } class Model { public double calcArea(double base, double height) { if(base <= 0 || height <=0) { throw new IllegalArgumentException(); } return (base * height) /2; } } class Program01 { public static void main(String[]args) { new Controller(); } } ===== Külön könyvtárakban, vázlat ===== Nagyobb projektekben a modell, nézet és kontroller számára is külön könyvtárat késztünk. A következő könyvtárszerkezetet alakítjuk ki: projektKonyvtar/ |--views/ | `--MainFrame.java |--models/ | `--Model.java |--controllers/ | `--Controller.java |--Makefile `--Haromszog.java package view; import javax.swing.JFrame; public class MainFrame extends JFrame { public MainFrame() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(400, 300); setVisible(true); } } package model; public class Model { } package controller; import view.MainFrame; import model.Model; public class Controller { MainFrame mainFrame = new MainFrame(); Model model = new Model(); public Controller() { } } import controller.Controller; class Haromszog { public static void main(String[] args) { new Controller(); } } SOURCE=view/MainFrame.java \ model/Model.java \ controller/Controller.java \ Haromszog.java all: javac -cp . $(SOURCE) run: xterm -e "java -cp . Haromszog; read" ===== Külön könyvtárakban, megvalósítva ===== A következőben a háromszög területszámító program komplett megvalósítását látjuk, egységteszttel együtt. Tesztelni csak a model részt teszteljük, ahol maga a számítás történik. projektKonyvtar/ |--views/ | `--MainFrame.java |--models/ | `--Model.java |--controllers/ | `--Controller.java |--Makefile |--Haromszog.java `--test/ `--ModelTest.java package view; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.JButton; import javax.swing.BoxLayout; import javax.swing.JPanel; import java.awt.FlowLayout; import java.awt.event.ActionListener; public class MainFrame extends JFrame { JPanel topPanel = new JPanel(); JLabel titleLabel = new JLabel("Háromszög területszámító"); JPanel bottomPanel = new JPanel(); JLabel baseLabel = new JLabel("alap:"); public JTextField baseField = new JTextField(5); JLabel heightLabel = new JLabel("magasság:"); public JTextField heightField = new JTextField(5); public JLabel resultLabel = new JLabel("eredmény:"); public JTextField resultField = new JTextField(5); JButton calcButton = new JButton("Számít"); public MainFrame() { topPanel.setLayout(new FlowLayout()); topPanel.add(titleLabel); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); bottomPanel.add(baseLabel); bottomPanel.add(baseField); bottomPanel.add(heightLabel); bottomPanel.add(heightField); bottomPanel.add(resultLabel); bottomPanel.add(resultField); bottomPanel.add(calcButton); resultLabel.setVisible(false); resultField.setVisible(false); setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); add(topPanel); add(bottomPanel); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } public void addCalcButtonActionListener(ActionListener listener) { calcButton.addActionListener(listener); } } package test; import org.junit.Test; import static org.junit.Assert.*; import model.Model; public class ModelTest { Model model = new Model(); @Test public void testCalcArea() { assertEquals(525, model.calcArea(30, 35), 0); assertEquals(900, model.calcArea(40, 45), 0); } @Test(expected = IllegalArgumentException.class) public void tesztExceptionSzamitTerulet() { model.calcArea(0, 35); } } package model; public class Model { public static double calcArea(double base, double height) { if (base <= 0 || height <= 0) { throw new IllegalArgumentException("Nem megfelelő paraméterek"); } return (base * height) / 2; } } package controller; import view.MainFrame; import model.Model; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class Controller { MainFrame mainFrame = new MainFrame(); Model model = new Model(); public Controller() { mainFrame.addCalcButtonActionListener(new ActionListener(){ public void actionPerformed(ActionEvent event) { String baseStr = mainFrame.baseField.getText(); double base = Double.parseDouble(baseStr); String heightStr = mainFrame.heightField.getText(); double height = Double.parseDouble(heightStr); Double result = model.calcArea(base, height); mainFrame.resultField.setText(result.toString()); mainFrame.resultField.setVisible(true); mainFrame.resultLabel.setVisible(true); mainFrame.pack(); } }); } } import controller.Controller; class Haromszog { public static void main(String[] args) { new Controller(); } } SOURCE=view/MainFrame.java \ model/Model.java \ controller/Controller.java \ Haromszog.java all: javac -cp . $(SOURCE) run: xterm -e "java -cp . Haromszog; read" LINJUNIT=/usr/share/java/junit4.jar testl: javac -cp .:$(LINJUNIT) test/ModelTest.java java -cp .:$(LINJUNIT) org.junit.runner.JUnitCore test.ModelTest WINJUNIT=c:\bin\SWScite\javalibs\junit-4.11.jar WINHAMCREST=c:\bin\SWScite\javalibs\hamcrest-core-1.3.jar testw: javac -cp .;$(WINJUNIT);$(WINHAMCREST) test/ModelTest.java java -cp .;$(WINJUNIT);$(HAMCREST) org.junit.runner.JUnitCore test.ModelTest ===== MVC NetBeansben ===== * view.Mainwindow osztályt hozzuk létre JFrame-ből * controller.Controller osztályt hozunk létre * model.Model osztályt hozunk létre package view; import controller.Controller; public class Mainwindow extends javax.swing.JFrame { Controller controller = new Controller(this); public Mainwindow() { initComponents(); } //... private void aboutButtonActionPerformed(java.awt.event.ActionEvent evt) { controller.aboutButtonActionListener(evt); } public static void main(String args[]) { //... } //.. private javax.swing.JButton aboutButton; } package controller; import java.awt.event.ActionEvent; import javax.swing.JFrame; import javax.swing.JOptionPane; import model.Model; public class Controller { Model model = new Model(); Mainwindow jframe; public Controller(Mainwindow jframe) { this.jframe = jframe; } public void aboutButtonActionListener(ActionEvent ev ) { JOptionPane.showMessageDialog(jframe, "Működik"); } } package model; public class Model { } ===== Swing GUI program MVC-ben ===== app01/ |-lib/ `-src/ |-controllers/ | `-MainController.java |-models/ | `-MainModel.java |-views/ | `-MainFrame.java `-App.java import controllers.MainController; public class App { public static void main(String[] args) throws Exception { new MainController(); } } package views; import javax.swing.JFrame; public class MainFrame extends JFrame { public MainFrame() { this.setTitle("App"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(400, 300); this.setVisible(true); } } package models; public class MainModel { } package controllers; import models.MainModel; import views.MainFrame; public class MainController { MainFrame mainFrame; MainModel mainModel; public MainController() { this.mainFrame = new MainFrame(); this.mainModel = new MainModel(); } } ===== Linkek ===== * https://www.geeksforgeeks.org/java-util-observable-class-java/ (2019) * https://www.concretepage.com/java/example-observer-observable-java (2019) * https://www.baeldung.com/java-observer-pattern (2019) * http://www.austintek.com/mvc/ (2019) * https://www.ssaurel.com/blog/learn-to-make-a-mvc-application-with-swing-and-java-8/ (2019) * https://examples.javacodegeeks.com/core-java/java-swing-mvc-example/ (2019) * https://web.archive.org/web/20131228060247/http://www.cs.wcupa.edu/~rkline/java/mvc-example.html (2019)