Teraz zajmiemy się wzorcami Observer, Visitor i Composite. Są one nie tylko bardzo ważne, ale i niezwykle ciekawe. Wzorzec Observer dobrze znamy z delegacyjnego modelu obsługi zdarzeń, natomaist Visitor, sczególnie w połączeniu z Composite, może stanowić istotne uzupełnienie umiejętności programistycznych.
package colorgoodies; import java.awt.*; import javax.swing.colorchooser.*; import javax.swing.event.*; import java.io.*; public class SwatchColorSelectionModel implements ColorSelectionModel, Serializable { protected transient ChangeEvent changeEvent = null; protected EventListenerList listenerList = new EventListenerList(); private Color[] colors; private Color selectedColor; private int selectedIndex = -1; public SwatchColorSelectionModel(Color[] c) { colors = c; selectedColor = null; } public SwatchColorSelectionModel(Color[] col, Color c) { this(col); for (int i=0; i<colors.length; i++) if (c.equals(colors[i])) { selectedIndex = i; selectedColor = c; break; } } public SwatchColorSelectionModel(Color[] col, int i) { this(col); try { selectedColor = colors[i]; selectedIndex = i; } catch (ArrayIndexOutOfBoundsException exc) { } } public int getSelectedIndex() { return selectedIndex; } public void setSelectedIndex(int value) { int old = selectedIndex; if (value != old) { if (value == -1) { selectedIndex = -1; setSelectedColor(null); } else try { selectedIndex = value; setSelectedColor(colors[value]); } catch (ArrayIndexOutOfBoundsException exc) { selectedIndex = old; } } } public void setSelectedColor(Color color) { if (color == null) { selectedColor = null; selectedIndex = -1; } else if (selectedColor == null || !selectedColor.equals(color)) { selectedColor = color; } fireStateChanged(); } public Color getSelectedColor() { return selectedColor; } public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } public ChangeListener[] getChangeListeners() { return (ChangeListener[])listenerList.getListeners( ChangeListener.class); } protected void fireStateChanged() {O zmianie wybranego koloru model powiadamia zainteresowanych słuchaczy za pomocą generacji zdarzenia changeEvent i rozesłania go po przyłączonych słuchaczach (metoda fireStateChanged()).
ChangeListener[] listeners = getChangeListeners();
for (int i=0; i<listeners.length; i++) {
if (changeEvent == null) {
changeEvent = new ChangeEvent(this);
}
listeners[i].stateChanged(changeEvent);
}
}
}
package colorgoodies; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.colorchooser.*; public class SwatchPanel extends JPanel { private Color[] colors; private int rows; private int columns; private Dimension swatchSize; private Dimension gap; private boolean tipsEnabled; private SwatchColorSelectionModel model; private int mouseX; private int mouseY; private ChangeListener modelListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { repaint(); } }; public ChangeListener getRepaintListener() { return modelListener; } public SwatchPanel(Color[] c, int r, Dimension s, Dimension g) { this(c,r,s,g,true); } public SwatchPanel(Color[] c, int r, Dimension s, Dimension g, boolean t) { init(c, r, s, g, t); setBackground(Color.white); addMouseListener(colorListener); } public void init(Color[] c, int r, Dimension s, Dimension g, boolean t) { colors = c; rows = r; swatchSize = s; gap = g; tipsEnabled = t; columns = colors.length / rows + (colors.length%rows > 0 ? 1 : 0); setTipsEnabled(tipsEnabled); } public void setModel(SwatchColorSelectionModel m) {Wydaje się, że pojawił się tu nadmiar kounikacyjny:
int oldSelInd = -1;
if (model != null) {
oldSelInd = model.getSelectedIndex();
model.removeChangeListener(modelListener);
}
model = m;
int newSelInd = m.getSelectedIndex();
model.addChangeListener(modelListener); if (oldSelInd != newSelInd) modelListener.stateChanged(new ChangeEvent(model)); } public SwatchColorSelectionModel getModel() { return model; } public void paintComponent(Graphics g) { g.setColor(getBackground()); g.fillRect(0,0,getWidth(), getHeight()); for (int row = 0; row < rows; row++) { for (int column = 0; column < columns; column++) { Color c = getColorForCell(column, row); if (c == null) return; g.setColor(c); int x = column * (swatchSize.width + gap.width); int y = row * (swatchSize.height + gap.height); int selectedInd = model.getSelectedIndex(); if (selectedInd != -1 && selectedInd == getIndexForLocation(x,y)) { g.fillOval( x, y, swatchSize.width, swatchSize.height); g.setColor(Color.black); g.drawOval( x, y, swatchSize.width-1, swatchSize.height-1); } else { g.fillRect( x, y, swatchSize.width, swatchSize.height); g.setColor(Color.black); g.drawRect( x, y, swatchSize.width-1, swatchSize.height-1); } } } } public Dimension getPreferredSize() { int x = columns * (swatchSize.width + gap.width) -1; int y = rows * (swatchSize.height + gap.height) -1; return new Dimension( x, y ); } public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getMaximumSize() { return getPreferredSize(); } public String getToolTipText(MouseEvent e) { if (!tipsEnabled) return null; Color color = getColorForLocation(e.getX(), e.getY()); if (color == null) return null; return color.getRed()+" "+ color.getGreen() + " " + color.getBlue(); } public Color getColorForLocation( int x, int y ) { int column = x / (swatchSize.width + gap.width); int row = y / (swatchSize.height + gap.height); return getColorForCell(column, row); } private Color getColorForCell( int column, int row) { int ind = (row * columns) + column; if (ind < colors.length) return colors[ind]; else return null; } public int getIndexForLocation( int x, int y) { int column = x / (swatchSize.width + gap.width); int row = y / (swatchSize.height + gap.height); int ind = (row * columns) + column; if (ind < colors.length) return ind; else return -1; } public void setSwatchSize(Dimension d) { swatchSize = d; } public Dimension getSwatchSize() { return swatchSize; } public void setRows(int n) { rows = n; columns = colors.length / rows + (colors.length%rows > 0 ? 1 : 0); } public int getRows() { return rows; } public void setGapSize(Dimension d) { gap = d; } public Dimension getGapSize() { return gap; } public void setTipsEnabled(boolean te) { tipsEnabled = te; String tip = (te ? "" : null); setToolTipText(tip); } public boolean isTipsEnabled() { return tipsEnabled; } static int jccind = -1; private ActionListener HSBlistener = new ActionListener() { public void actionPerformed(ActionEvent e) { if (jccind == -1) return; if (e.getActionCommand().equals("OK") ) { colors[jccind] = jcc.getColor(); repaint(); } jccind = -1; } }; private MouseListener colorListener = new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.isMetaDown()) return; // opcja z menu kontekstowego - obsługa nie tu int ind = getIndexForLocation(e.getX(), e.getY()); if (ind == -1) return; if (e.isControlDown()) { // Zmiana swatcha jccind = ind; JDialog d = JColorChooser.createDialog(SwatchPanel.this, "Choose color", true,jcc, HSBlistener, HSBlistener); d.show(); } else { mouseX = e.getX(); mouseY = e.getY(); model.setSelectedIndex( ind ); // Zmiana w modelu
}
}
};
public int getMouseX() { return mouseX; }
public int getMouseY() { return mouseY; }
private static JColorChooser jcc;
static {
jcc = new JColorChooser();
AbstractColorChooserPanel[] p = jcc.getChooserPanels();
jcc.removeChooserPanel(p[0]);
jcc.removeChooserPanel(p[2]);
}
}
private ChangeListener colorListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { if (e.getSource() != cbp.getModel()) return; Color c = cbp.getModel().getSelectedColor(); if (c == null) return; String[] types = view.getTypes(); if (!(view.getTextComponent() instanceof JTextPane) || attrsToTypes.get(attrs[view.getCurrentMode()]) == null) view.set(attrs[view.getCurrentMode()], c); else view.set(types[view.getCurrentType()], attrs[view.getCurrentMode()], c); } };Tutaj:
private PropertyChangeListener viewPropsChanged = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { String propName = e.getPropertyName(); // ... int newVal = ((Integer)e.getNewValue()).intValue(); int cmode = view.getCurrentMode(); int ctype = view.getCurrentType(); boolean ta = attrsToTypes.get(attrs[cmode]) != null; SwatchColorSelectionModel newModel = null; if (propName.equals("Type")) newModel = csm[cmode][ ta ? newVal : 0]; else if (propName.equals("Mode")) newModel = csm[newVal][ta ? ctype : 0]; else return; cbp.getModel().removeChangeListener(colorListener); cbp.setModel(newModel); newModel.addChangeListener(colorListener); } };
public class LCompiler {
private ArrayList errorList;
private PropertyChangeSupport change = new PropertyChangeSupport(this);
// obiekt-kompilator
private com.sun.tools.javac.Main compiler = new com.sun.tools.javac.Main();
// ....
private static final LCompiler comp = new LCompiler();
public static LCompiler getCompiler() { return comp; }
public static final int STOPPED = 0, RUNNING = 1;
private int compilerState = STOPPED;
private int returnCode = -1;
public int getReturnCode() {
return returnCode;
}
public String getErrorInfo() {
return errorInfo;
}
private LCompiler() { }
public void compile(String[] args) {
StringWriter sout = new StringWriter();
PrintWriter pout = new PrintWriter(sout);
change.firePropertyChange("compilerState", STOPPED, RUNNING);
returnCode = compiler.compile(args, pout);
errorInfo = "";
if (returnCode != 0) {
errorList = new ArrayList();
SrcError err = null;
try {
BufferedReader br = new BufferedReader(
new StringReader(sout.toString())
);
String line;
while ((line = br.readLine()) != null) {
String tmp = line.trim();
if (tmp.equals("")) continue;
matcher.reset(line);
if (matcher.matches()) {
err = new SrcError (Integer.parseInt(matcher.group(2)), matcher.group(3));
errorList.add(err);
}
else if (line.startsWith("Note")) errorList.add(new SrcError(-1, line));
else if (Character.isDigit(line.charAt(0))) errorInfo += line + " ";
else {
if (err == null) errorList.add( new SrcError(-1, line));
else err.addDesc(line);
}
}
br.close();
} catch (Exception exc) { exc.printStackTrace(); }
}
change.firePropertyChange("compilerState", RUNNING, STOPPED);
}
// .... metody dodawania/usuwania słuchaczy zmian właściwości
Obserwatorem będzie tu głównie klasa ErrList, która prezentuje w IDE wyniki kompilacji:class JLErrs extends JList { // .... } public class ErrList extends JPanel { //........... JLErrs errs; JLabel info = new JLabel(" "); public ErrList(View v) { super(new BorderLayout()); view = v; errs = new JLErrors(v); info.setOpaque(true); info.setBackground(Color.white); LCompiler compiler = LCompiler.getCompiler(); compiler.addPropertyChangeListener(this); add(info, "North"); add (new JScrollPane(errs)); Dimension d = new Dimension(300, 100); setPreferredSize(d); setMaximumSize(d); setMinimumSize(d); errs.addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { selectError(e); } }); } // ..... public void propertyChange(PropertyChangeEvent e) {
final LCompiler compiler = LCompiler.getCompiler(); if (!e.getPropertyName().equals("compilerState")) return; DockableWindowManager wm = view.getDockableWindowManager(); wm.showDockableWindow("ErrList"); int old = ( (Integer) e.getOldValue()).intValue(); int newv = ( (Integer) e.getNewValue()).intValue(); if (newv == LCompiler.RUNNING) { SwingUtilities.invokeLater( new Runnable() { public void run() { ListModel lm = errs.getModel(); if (lm instanceof ElistModel) ((ElistModel) lm).clear(); info.setText("Compiling..."); } }); } else if (newv == LCompiler.STOPPED) { final int rc = compiler.getReturnCode(); SwingUtilities.invokeLater( new Runnable() { public void run() { if (rc == 0) { info.setText("Compilation finished. Success"); } else info.setText("Compilation finished. " +compiler.getErrorInfo()); } }); if (rc == 0) { if (JLECompiler.isRealJava) new JavaRunner(); else new Runner().start(); } final java.util.List elist = compiler.getErrorList(); if (elist == null) return; SwingUtilities.invokeLater( new Runnable() { public void run() { errs.setModel(new ElistModel(elist)); } }); } }
abstract class Pacjent { protected String name; Pacjent(String s) { name = s; } // *** funkcjonalność zapisana w klasie public abstract String lecz(); } class ChoryNaGłowę extends Pacjent { private static final String opis = "Chory na głowę"; ChoryNaGłowę(String s) { super(s); } public String toString() { return name + " " + opis; } // ---- funkcjonalność zapisana w klasie public String lecz() { return "Stosuję aspirynę"; } } class ChoryNaŻołądek extends Pacjent { private static final String opis = "Chory na żołądek"; ChoryNaŻołądek(String s) { super(s); } public String toString() { return name + " " + opis; } public String lecz() { return "Stosuję węgiel"; } } class ChoryNaNogę extends Pacjent { private static final String opis = "Chory na nogę"; ChoryNaNogę(String s) { super(s); } public String toString() { return name + " " + opis; } public String lecz() { return "Zakładam gips"; } }Ale:
interface Visitor { public void visit(ChoryNaGłowę p); public void visit(ChoryNaŻołądek p); public void visit(ChoryNaNogę p); } class Leczenie2 implements Visitor { public void visit(ChoryNaGłowę p) { System.out.println(p); System.out.println("Stosuję aspirynę"); } public void visit(ChoryNaŻołądek p) { System.out.println(p); System.out.println("Stosuję węgiel"); } public void visit(ChoryNaNogę p) { System.out.println(p); System.out.println("Zakładam gips"); } }W klasach pacjentów musimy dodać metody akceptacji - wywołujące metodę visit przekazanego wizytora.
abstract class Pacjent { protected String name; Pacjent(String s) { name = s; } public abstract void accept(Visitor v); } class ChoryNaGłowę extends Pacjent { private static final String opis = "Chory na głowę"; ChoryNaGłowę(String s) { super(s); } public String toString() { return name + " " + opis; } public void accept(Visitor v) {No i teraz zastosujemy double-dispatching w działaniu:
v.visit(this);
}
} class ChoryNaŻołądek extends Pacjent { private static final String opis = "Chory na żołądek"; ChoryNaŻołądek(String s) { super(s); } public String toString() { return name + " " + opis; } public void accept(Visitor v) {
v.visit(this);
}
} class ChoryNaNogę extends Pacjent { private static final String opis = "Chory na nogę"; ChoryNaNogę(String s) { super(s); } public String toString() { return name + " " + opis; } public void accept(Visitor v) {
v.visit(this);
}
}
public static void main(String[] args) { List<Pacjent> lista = new ArrayList<Pacjent>(); lista.add(new ChoryNaGłowę("Jan Kowalski")); lista.add(new ChoryNaGłowę("Stefan Kowalewski")); lista.add(new ChoryNaNogę("Janusz Malinowski")); lista.add(new ChoryNaŻołądek("Adam Mickiewicz")); // Leczenie Visitor v = new Leczenie2(); for(Pacjent p : lista) p.accept(v); }
// dodajemy nową funkcjonalność (zewnętrznie!) // w klasach pacjentów nic nie musimy zmieniać class Wypis implements Visitor { public void visit(ChoryNaGłowę p) { System.out.println(p); System.out.println("Do domu!"); } public void visit(ChoryNaŻołądek p) { System.out.println(p); System.out.println("Do domu, ale stosować dietę"); } public void visit(ChoryNaNogę p) { System.out.println(p); System.out.println("Należy przewieźć do domu"); } }No i teraz możemy pacjentów nie tylko leczyć ale i wypisywać do domu.
List<Pacjent> lista = new ArrayList<Pacjent>(); // ... // Leczenie Visitor leczenie = new Leczenie2(); for(Pacjent p : lista) p.accept(leczenie); // Wypisy Visitor wypis = new Wypis(); for(Pacjent p : lista) p.accept(wypis);
static void wykonajOperację(Visitor v, List<Pacjent> lista) { System.out.println("Wykonuję operację - " + v); for(Pacjent p : lista) p.accept(v); // Ho, ho! efekt wwwołania p.accept(v) jest polimorficzny // zarówno względem p jak i względm v. }
List<Pacjent> lista = new ArrayList<Pacjent>(); // .... Visitor leczenie = new Leczenie2(); wypis = new Wypis(); wykonajOperację(leczenie, lista); wykonajOperację(wypis, lista);co w końcu da nam wynik:
import java.lang.reflect.*; import javax.swing.*; import java.awt.*; interface DynamicVisitor { public void visit(Object o); } abstract class AbstractDynamicVisitor implements DynamicVisitor { public void visit(Object o) { // Klasa konkretnego wizytora Class visitorClass = getClass(); // Klasa wizytowanego obiektu Class objectClass = o.getClass(); // W konkretnym wizytorze szukamy metody visit dla danego obiektu try { Method visitMethod = visitorClass.getMethod("visit", objectClass); visitMethod.invoke(this,o); } catch (Exception exc) { defaultDispatch(o); } } // Jeśli dla danej klasy obiektu nie ma metody visit z takim argumentem public void defaultDispatch(Object o) {} } class PrzydziałDoOddziałów extends AbstractDynamicVisitor { public void visit(ChoryNaGłowę p) { System.out.println(p + " - na oddział psychiatrii"); } public void visit(ChoryNaNogę p) { System.out.println(p + " - ortopedia"); } public void visit(ChoryNaŻołądek p) { System.out.println(p + " - na oddział zakaźny"); } public void defaultDispatch(Object o) { System.out.println(o + " - przypadek nierozpoznany"); } } public class Visitor2 extends JFrame { public Visitor2() { java.util.List<Pacjent> lista = new java.util.ArrayList<Pacjent>(); lista.add(new ChoryNaGłowę("Jan Kowalski")); lista.add(new ChoryNaGłowę("Stefan Kowalewski")); lista.add(new ChoryNaNogę("Janusz Malinowski")); lista.add(new ChoryNaŻołądek("Adam Mickiewicz")); // Uwaga! Teraz nie musimy stosować metody accept!!! DynamicVisitor dv = new PrzydziałDoOddziałów(); for(Pacjent p : lista) dv.visit(p); // klasy nie muszą nic wiedzieć o tym, że są wizytowane // czyli możemy coś robić "z zewnątrz" zamkniętym, gotowym klasom setTitle("Kliknij Gui change - zmieni się wygląd"); final DynamicVisitor guiChanger = new AbstractDynamicVisitor() { public void visit(JButton b) { b.setBackground(Color.yellow); } public void visit(JTextField tf) { tf.setBorder(BorderFactory.createLineBorder(Color.red, 3)); } }; setLayout(new FlowLayout()); add(new JButton("Jakiś przycisk")); add(new JTextField(10)); add(new JButton("Inny przycisk")); JButton chgGui = new JButton("Gui change"); chgGui.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { Component[] clist = getContentPane().getComponents(); for (Component c : clist) if (c != e.getSource()) guiChanger.visit(c); } }); add(chgGui); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } public static void main(String[] args) { Visitor2 visitor2 = new Visitor2(); } }Zobacz demo programu.
import java.util.*; interface Component { void show(); } class Leaf implements Component { private String id; private int value; public Leaf(String s, int v) { id = s; value = v; } public String toString() { return id + " " + value; } public void show() { System.out.println(this); } } class Node extends LinkedList<Component> implements Component { private String id; public Node(String s) { id = s; } public void show() { // rekursywnie! System.out.println(id); for(Component c : this ) c.show(); } } class Composite { public static void main(String args[]) { Node root = new Node("Zwierzęta"); Node node; root.add(node = new Node("Psy")); node.add(new Leaf("Azor", 5)); node.add(new Leaf("Aza", 7)); root.add(node = new Node("Koty")); node.add(new Leaf("Pusia", 5)); node.add(new Leaf("Mruczek", 10)); root.show(); // jakże wygodne! } }
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class KontKomp extends JFrame { public KontKomp() { setLayout(new FlowLayout()); JPanel p1 = new JPanel(); p1.add(new JButton("B1")); p1.add(new JButton("B1")); JPanel p2 = new JPanel(); p2.add(new JButton("BUTT3")); p2.add(new JButton("BUTT4")); add(p1); add(p2); JButton show = new JButton("Zmiana koloru"); show.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground((JComponent) getContentPane(), Color.yellow); } }); add(show); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void setBackground(JComponent jc, Color color) { jc.setBackground(color); java.awt.Component[] components = jc.getComponents(); for (java.awt.Component c : components) setBackground( (JComponent) c, color); } public static void main(String[] args) { KontKomp kont = new KontKomp(); kont.pack(); kont.setLocationRelativeTo(null); kont.show(); }Zobacz demo programu:
public void setBackground(Component jc, Color color) { jc.setBackground(color); if (jc instanceof Container) { Component[] components = jc.getComponents(); for (Component c : components) setBackground( (JComponent) c, color); } }
import java.util.*; interface Component { void accept(Visitor v); } interface Visitor { void visit(Leaf l); void visit(Node n); } class Leaf implements Component { private String id; private int value; public Leaf(String s, int v) { id = s; value = v; } public int getValue() { return value; } public void setValue(int i) { value = i; } public String toString() { return id + " " + value; } public void accept(Visitor v) {
v.visit(this);
} } class Node extends LinkedList<Component> implements Component { private String id; public Node(String s) { id = s; } public String getId() { return id; } public void accept(Visitor v) {
v.visit(this);
}
} class PrintVisitor implements Visitor { public void visit(Leaf l) { System.out.println(l); } public void visit(Node n) { System.out.println(n.getId()); for(Component c : n) c.accept(this); // Kluczowe! // nie wolno: System.out.println(c); } } class IncreaseValueVisitor implements Visitor { public void visit(Leaf l) { l.setValue(l.getValue()+1); } public void visit(Node n) { for(Component c : n) c.accept(this); } } class Composite { public static void main(String args[]) { Node root = new Node("Zwierzęta"); Node node; root.add(node = new Node("Psy")); node.add(new Leaf("Azor", 5)); node.add(new Leaf("Aza", 7)); root.add(node = new Node("Koty")); node.add(new Leaf("Pusia", 5)); node.add(new Leaf("Mruczek", 10)); root.accept(new IncreaseValueVisitor()); root.accept(new PrintVisitor()); } }
Kwiaty mogą być kupowane w skrzynkach.
Skrzynki mogą być ładowane na samochody.
Samochody mogą być przewożone promami.
Zmienić architekturę aplikacji tak by w w/w warunkach można było zastosować
wzorce Composite i Visitor.
Zapewnić możliwość dodawania dowolnych visitorów.
Jako przykład: visitor obliczający koszt kwiatów i visitor obliczający ilość kwiatów.