/ janturon.cz / Výuka / Java / Architektura MVC

Architektura MVC

Události komponent

Připomeňme si kód události zavření okna:

this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } });

Událost tlačítka addButton z předchozí lekce (jediná v aplikaci) bude mít podobný kód. Metoda pro přidání posluchače tlačítka se nazývá addActionListener a očekává objekt typu ActionEvent implementující metodu actionPerformed:

addButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // převedení na číslo obsahu pole phone int phoneNumber = Integer.parseInt(phone.getText()); // vytvoření objektu Contact z polí name a phone Contact c = new Contact(name.getText(),phoneNumber); // přidání položky do pole list list.add(c.toString()); // vymazání obsahu polí name a phone name.setText(""); phone.setText(""); // přesunutí kurzoru do pole name name.requestFocusInWindow(); } });

Všechny metody posluchačů mají parametr popisující událost. Využít ji můžeme třeba u políčka phone, kde můžeme povolit zápis pouze čísel:

phone.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent e) { // zjištění stisknuté klávesy char c = e.getKeyChar(); // number = je číslo nebo backspace boolean number = Character.isDigit(c) || c=='\b'; // není-li číslo, potlač událost if(!number) e.consume(); } });

MVC

Všechen tento kód cpe návrhář do konstruktoru. To není objektové programování. Smyslem architektury MVC je rozdělit kód do tří částí:

Nejdůležitější je oddělení Modelu, který popisuje manipulaci s daty aplikace. K němu je pak možné tvořit mnoho Pohledů (třeba formulář pro účetní, správce, zákazníka). Zároveň je pak možné změnit obsah návratových hodnot modelu a změny se automaticky projeví ve všech Pohledech.

Analytici často používají zaklínadlo (buzzword) znovupoužitelnost (reusability) znamenající možnost použít kód na více místech. Kdyby například Model přímo měnil obsah list, znamená to, že je pevně svázán (tightly/strong coupled) s Pohledem a není možné ho použít v jiném formuláři. Spojení proto musí být volné (loose coupling), tj. pomocí rozhraní a vzájemného volání pouze přes parametry metod. Ono rozhraní se nazývá Observer, Java ho dokonce v balíku java.util přímo podporuje.

Model

import java.util.*; class Contact { private String name; private int phone; public static Contact nullItem = new Contact("",0); public Contact(String name, int phone) { this.name = name; this.phone = phone; } public String toString() { return name+": "+Integer.toString(phone); } @Override public boolean equals(Object o) { Contact c = (Contact)o; return name.equalsIgnoreCase(c.name) && phone==c.phone; } } public class Model extends Observable { private ArrayList contacts = new ArrayList(); public void addContact(Contact contact) { if(contacts.contains(contact)) return; setChanged(); contacts.add(contact); notifyObservers(contact); } }

Výraz @Override je nepovinný, je však dobré ho uvádět, protože kompilátor zkontroluje, jestli metodu opravdu překrýváme (tedy přesný název a typ), také čtenáři usnadňuje orientaci. Metodu equals překrýváme z třídy Object proto, abychom mohli objekty porovnávat v metodě contains třídy ArrayList.

Z rodiče Observable používáme metodu setChanged(), která oznamuje, že se Model změnil a příští volání notifyObservers(p) zavolá metodu update(model,p) na všech zaregistrovaných (viz Controller) posluchačích (tj. implementujícím Observer viz View).

View

import java.awt.*; import java.awt.event.*; import java.util.*; class ClosingFrame extends Frame { public ClosingFrame() { super(); addWindowListener(closer); } public ClosingFrame(String name) { super(name); addWindowListener(closer); } private static WindowAdapter closer = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; } public class View extends ClosingFrame implements Observer { private Panel top = new Panel(new FlowLayout()); private Button addButton = new Button("+"); private TextField name = new TextField(20); private TextField phone = new TextField(15); // pozor List je v java.awt i v java.util private java.awt.List list = new java.awt.List(5); private static KeyAdapter numberAdapter = new KeyAdapter() { public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); boolean number = Character.isDigit(c) || c=='\b'; if(!number) e.consume(); } }; public View() { super("Phone List"); setLayout(new BorderLayout()); add(top,BorderLayout.NORTH); add(list,BorderLayout.SOUTH); top.add(name); top.add(phone); top.add(addButton); phone.addKeyListener(numberAdapter); pack(); setVisible(true); } private void resetInputFields() { name.setText(""); phone.setText(""); name.requestFocusInWindow(); } @Override public void update(Observable obs, Object obj) { Contact contact = (Contact)obj; list.add(contact.toString()); resetInputFields(); } // umožnit Controlleru napojit Model public void addAddButtonController(ActionListener listener) { addButton.addActionListener(listener); } public String getName() { return name.getText().trim(); } public int getPhone() { try { return Integer.parseInt(phone.getText().trim()); } catch(NumberFormatException e) { return 0; } } }

Controller

import java.awt.event.*; class PhoneList { public static void main(String args[]) { final View view = new View(); final Model model = new Model(); // zaregistrování posluchače model.addObserver(view); // obsluha událostí view.addAddButtonController(new ActionListener() { public void actionPerformed(ActionEvent e) { String name = view.getName(); int phone = view.getPhone(); if(name.isEmpty() || phone==0) return; model.addContact(new Contact(name,phone)); } }); } }