Übung zur Vorlesung Einführung in Software Engineering Wintersemester 2012/13, Richard Bubel und Martin Hentschel Übungsblatt 9: Design Patterns Abgabeformat: Reichen Sie Ihre Lösung per SVN ein. Beachten Sie die vorgegebenen Verzeichnisse und Dateinamen der jeweiligen Aufgabenstellung. Während der Übungsbearbeitung können Sie Ihre Lösungen beliebig oft in das SVN hochladen (per Commit). Wir prüfen die Zeit der Einreichung Ihrer Lösungen unter der Benutzung des SVN Zeitstempels. Abgabetermin: 31.01.2013 - 08:00 Uhr Aufgabe 9.1 (4,5 Punkte) Ziel: Entwurfsmuster verstehen und anwenden. Beschreibung: Die Anwendung „Cash Register“ implementiert eine einfache Registrierkasse, wie sie zum Beispiel in einer Bäckerei zum Einsatz kommen könnte. Ihr Hauptanwendungsfall ist die Berechnung des Gesamtbetrags eines Kassenbelegs. Dazu gibt der Anwender der Reihe nach die gekauften Artikel jeweils mit Anzahl, Preis und einer kurzen Beschreibung in das System ein. Bereits erfasste Artikel und deren Gesamtpreis sind stets einsehbar und werden nach dem Hinzufügen eines neuen Artikels aktualisiert. Abbildung 1 zeigt einen beispielhaften Kassenbeleg, wie er in der Anwendung erfasst wurde. Abbildung 1: Screenshot „Cash Register“ a) Ziel: Entwurfsmuster „Strategy“ anwenden. (2 Punkte) Beschreibung: Die Anwendung „Cash Register“ soll dem Benutzer zukünftig erlauben, Positionen eines Kassenbelegs mit Hilfe eines Filters zu suchen. Trägt der Anwender einen Text in das Feld „Filter“ ein, wird zeitgleich der angezeigte Kassenbeleg aktualisiert und nur noch Artikel in denen der eingegebene Text in der Beschreibung enthalten ist, angezeigt. Ist kein Text im Feld „Filter“ definiert, wird der gesamte Kassenbeleg mit allen Positionen angezeigt. Abbildung 2 zeigt den nach „Ro“ gefilterten Kassenzettel aus Abbildung 1. Aufgabe: Realisieren Sie die neue Funktionalität mit Hilfe des Entwurfsmuster „Strategy“. Nutzen Sie als Ausgang den auf der Vorlesungswebseite https://www.se.tu-darmstadt.de/teaching/courses/einfuehrung-in-softwareengineering/material-zur-vorlesung-und-uebung verfügbaren Quellcode für Aufgabe 9.1a. Reichen Sie Ihre Lösung per SVN als ausführbares Eclipse Java Projekt ein. Dieses muss mit dem Namen „ex09_1a“ unter „trunk“ abgelegt werden. Hinweis: Kommentare im Quellcode mit Hilfestellungen beginnen mit „// TODO“. 1 Übung zur Vorlesung Einführung in Software Engineering Abbildung 2: Screenshot „Cash Register“ mit Filter b) Ziel: Entwurfsmuster „Observer“ anwenden. (2,5 Punkte) Beschreibung: Die Anwendung „Cash Register“ soll zukünftig den selben Kassenbeleg in mehreren Fenstern darstellen und editieren können. Entscheidend ist, dass die Anzeige in allen Fenstern umgehend aktualisiert wird, sobald eine neue Position dem Kassenbeleg hinzugefügt wurde. Abbildung 3 zeigt, wie der selbe Kassenbeleg in zwei Fenstern dargestellt wird. Abbildung 3: Screenshot „Cash Register“ mit zwei Fenstern Aufgabe: Realisieren Sie die neue Funktionalität mit Hilfe des Entwurfsmuster „Observer“. Nutzen Sie als Ausgang den auf der Vorlesungswebseite https://www.se.tu-darmstadt.de/teaching/courses/einfuehrung-in-softwareengineering/material-zur-vorlesung-und-uebung verfügbaren Quellcode für Aufgabe 9.1b. Reichen Sie Ihre Lösung per SVN als ausführbares Eclipse Java Projekt ein. Dieses muss mit dem Namen „ex09_1b“ unter „trunk“ abgelegt werden. Hinweis: Kommentare im Quellcode mit Hilfestellungen beginnen mit „// TODO“. 2 Übung zur Vorlesung Einführung in Software Engineering Lösung a: Definition und Implementierung der Strategie: Listing 1: IReceiptPositionFilter 1 2 3 4 5 6 7 package de.tud.se. cashregister . printer . filter ; import de.tud.se. cashregister .model. ReceiptPosition ; public interface IReceiptPositionFilter { public boolean accept ( ReceiptPosition position ); } Listing 2: AllReceiptFilter 1 2 3 4 5 6 7 8 9 10 package de.tud.se. cashregister . printer . filter ; import de.tud.se. cashregister .model. ReceiptPosition ; public class AllReceiptFilter implements IReceiptPositionFilter { @ Override public boolean accept ( ReceiptPosition position ) { return true; } } Listing 3: DescriptionReceiptPositionFilter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package de.tud.se. cashregister . printer . filter ; import de.tud.se. cashregister .model. ReceiptPosition ; public class DescriptionReceiptPositionFilter implements IReceiptPositionFilter { private String filter ; public DescriptionReceiptPositionFilter ( String filter ) { this. filter = filter ; } @ Override public boolean accept ( ReceiptPosition position ) { if ( position != null) { String description = position . getDescription (); return description != null && description . contains ( filter ); } else { return false; } } } Verwendung der Strategie im Kontext: Listing 4: ReceiptPrinter 1 2 3 4 5 6 7 8 9 package de.tud.se. cashregister . printer ; import java.math. BigDecimal ; import de.tud.se. cashregister .model. Receipt ; import de.tud.se. cashregister .model. ReceiptPosition ; import de.tud.se. cashregister . printer . filter . IReceiptPositionFilter ; public class ReceiptPrinter { 3 Übung zur Vorlesung Einführung in Software Engineering 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 } private IReceiptPositionFilter filter ; public ReceiptPrinter ( IReceiptPositionFilter filter ) { this. filter = filter ; } // Pass the IReceiptPositionFilter alternatively as method parameter public String printReceipt ( Receipt receipt ) { StringBuffer sb = new StringBuffer (); sb. append (" Quantity "); sb. append (’\t’); sb. append ("Price"); sb. append (’\t’); sb. append (" Description "); sb. append (’\t’); sb. append ("Sum"); sb. append (’\n’); for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append ("\n"); BigDecimal totalSum = BigDecimal .ZERO; ReceiptPosition [] positions = receipt . getPositions (); for ( ReceiptPosition position : positions ) { if ( filter == null || filter . accept ( position )) { sb. append ( position . getQuantity ()); sb. append (’\t’); sb. append ( position . getPrice ()); sb. append (’\t’); sb. append ( position . getDescription ()); sb. append (’\t’); sb. append ( position . computePositionSum ()); sb. append (’\n’); totalSum = totalSum .add( position . computePositionSum ()); } } for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append (’\n’); sb. append (’\t’); sb. append (’\t’); sb. append (’\t’); sb. append ( totalSum ); return sb. toString (); } Anpassung der Benutzeroberfläche: Listing 5: CashRegisterFrame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package de.tud.se. cashregister .ui; import import import import import import import import java.awt. BorderLayout ; java.awt. GridBagConstraints ; java.awt. GridBagLayout ; java.awt. Insets ; java.awt.event. ActionEvent ; java.awt.event. ActionListener ; java.math. BigDecimal ; java.math. BigInteger ; import javax.swing. JButton ; import javax.swing. JFrame ; import javax.swing. JLabel ; 4 Übung zur Vorlesung Einführung in Software Engineering 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 import import import import import import import javax.swing. JOptionPane ; javax.swing. JPanel ; javax.swing. JScrollPane ; javax.swing. JTextArea ; javax.swing. JTextField ; javax.swing.event. DocumentEvent ; javax.swing.event. DocumentListener ; import import import import import import de.tud.se. cashregister .model. Receipt ; de.tud.se. cashregister .model. ReceiptPosition ; de.tud.se. cashregister . printer . ReceiptPrinter ; de.tud.se. cashregister . printer . filter . AllReceiptFilter ; de.tud.se. cashregister . printer . filter . DescriptionReceiptPositionFilter ; de.tud.se. cashregister . printer . filter . IReceiptPositionFilter ; public class CashRegisterFrame extends JFrame { /** * Generated UID. */ private static final long serialVersionUID = -3008711233508194058 L; private JTextArea receiptArea ; private JTextField quantityField ; private JTextField priceField ; private JTextField descriptionField ; private JButton addButton ; private JTextField filterField ; private Receipt receipt ; public CashRegisterFrame ( Receipt receipt ) { // Initialize frame super("Cash Register "); this. receipt = receipt ; setLocationByPlatform (true); setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE ); setLayout (new BorderLayout ()); // Create receipt area receiptArea = new JTextArea (20, 40); receiptArea . setEditable (false); add(new JScrollPane ( receiptArea ), BorderLayout . CENTER ); // Create fields JPanel fieldPanel = new JPanel (new GridBagLayout ()); add(fieldPanel , BorderLayout .SOUTH); GridBagConstraints c = new GridBagConstraints (); c. insets = new Insets (5, 5, 5, 5); c.fill = GridBagConstraints .BOTH; // Create quantity field and label quantityField = new JTextField (7); quantityField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel quantityLabel = new JLabel (" Quantity "); quantityLabel . setDisplayedMnemonic (’Q’); quantityLabel . setLabelFor ( quantityField ); fieldPanel .add( quantityLabel , c); fieldPanel .add( quantityField , c); // Create price field and label 5 Übung zur Vorlesung Einführung in Software Engineering 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 priceField = new JTextField (7); priceField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel priceLabel = new JLabel ("Price"); priceLabel . setDisplayedMnemonic (’P’); priceLabel . setLabelFor ( priceField ); fieldPanel .add(priceLabel , c); fieldPanel .add(priceField , c); // Create description field and label descriptionField = new JTextField (26); descriptionField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel descriptionLabel = new JLabel (" Description "); descriptionLabel . setDisplayedMnemonic (’D’); descriptionLabel . setLabelFor ( descriptionField ); fieldPanel .add( descriptionLabel , c); c. weightx = 1; fieldPanel .add( descriptionField , c); // Create add button addButton = new JButton ("Add"); addButton . setMnemonic (’A’); addButton . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { addReceiptPosition (); } }); c. weightx = 0; fieldPanel .add(addButton , c); // Create filter field and label filterField = new JTextField (); filterField . getDocument (). addDocumentListener (new DocumentListener () { @ Override public void removeUpdate ( DocumentEvent e) { updateShownReceipt (); } @ Override public void insertUpdate ( DocumentEvent e) { updateShownReceipt (); } @ Override public void changedUpdate ( DocumentEvent e) { updateShownReceipt (); } }); JLabel filterLabel = new JLabel (" Filter "); filterLabel . setDisplayedMnemonic (’F’); filterLabel . setLabelFor ( filterField ); c.gridy = 1; c. weightx = 0; fieldPanel .add( filterLabel , c); c. weightx = 1; c. gridwidth = 6; fieldPanel .add( filterField , c); // Optimize frame size pack (); 6 Übung zur Vorlesung Einführung in Software Engineering 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 } // Show initial receipt updateShownReceipt (); // Set focus to initial control quantityField . requestFocus (); } protected void clickOnAddButton () { addButton . doClick (); } protected void addReceiptPosition () { try { // Get values BigInteger quantity = new BigInteger ( quantityField . getText ()); BigDecimal price = new BigDecimal ( priceField . getText ()); String description = descriptionField . getText (); // Make sure that values are valid if ( quantity . compareTo ( BigInteger .ZERO) <= 0) { throw new IllegalStateException (" Quantity <= 0."); } if ( description == null || description .trim (). isEmpty ()) { throw new IllegalStateException (" Description not defined ."); } // Add receipt position ReceiptPosition newPosition = new ReceiptPosition (quantity , price , description ); receipt . addPosition ( newPosition ); // Update UI controls quantityField . setText (null); priceField . setText (null); descriptionField . setText (null); // Update shown receipt2 updateShownReceipt (); // Update focus quantityField . requestFocus (); } catch ( Exception e) { // Show error message JOptionPane . showMessageDialog (this , e, " Error ", JOptionPane . ERROR_MESSAGE ); // Update focus quantityField . requestFocus (); } } protected void updateShownReceipt () { // Get filter text String filterText = filterField . getText (); // Create new text to show in receipt area IReceiptPositionFilter filter = filterText != null && ! filterText . isEmpty () ? new DescriptionReceiptPositionFilter ( filterText ) : new AllReceiptFilter (); ReceiptPrinter printer = new ReceiptPrinter ( filter ); String receiptText = printer . printReceipt ( receipt ); // Update shown text receiptArea . setText ( receiptText ); } 7 Übung zur Vorlesung Einführung in Software Engineering Lösung b (Variante Observer): Diese Lösung verwendet die Klasse java.util.Observable zum Verwalten der Beobachter als Realisierung des Interfaces java.util.Observer. Eine Schwachstelle der Klasse ist, dass das Ereignis-Objekt, welches an die Beobachter mittels notifyObservers(Object) übergeben wird, nicht klar definiert ist. Somit sind Typumwandlungen (Cast) in den benötigten Typ notwendig und eine einheitliche Verwendung ist selbst in einem Projekt nicht garantiert. Zum anderen ist das Informieren der Beobachter nicht threadsicher, denn zwischen setChanged() und notifyObservers() könnte ein anderer Thread den Geändert-Zustand zurücksetzen, so dass kein Ereignis geworfen wird. Diesen Fehler in komplexen Anwendung zu finden ist sehr schwer, denn er tritt nur sporadisch auf. Daher sollte diese Lösung nur mit Vorsicht verwendet werden. Über Zustandsänderungen im Subjekt informieren: Listing 6: Receipt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package de.tud.se. cashregister .model; import import import import java.math. BigDecimal ; java.util. LinkedList ; java.util.List; java.util. Observable ; public class Receipt extends Observable { private List < ReceiptPosition > positions = new LinkedList < ReceiptPosition >(); public void addPosition ( ReceiptPosition position ) { if ( positions .add( position )) { setChanged (); notifyObservers ( position ); // Resets changed flag via clearChanged () } } public ReceiptPosition [] getPositions () { return positions . toArray (new ReceiptPosition [ positions .size () ]); } public BigDecimal computeReceiptSum () { BigDecimal totalSum = BigDecimal .ZERO; for ( ReceiptPosition position : positions ) { totalSum = totalSum .add( position . computePositionSum ()); } return totalSum ; } } Auf Zustandsänderungen im Subjekt reagieren: Listing 7: CashRegisterFrame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package de.tud.se. cashregister .ui; import import import import import import import import import import java.awt. BorderLayout ; java.awt. GridBagConstraints ; java.awt. GridBagLayout ; java.awt. Insets ; java.awt.event. ActionEvent ; java.awt.event. ActionListener ; java.math. BigDecimal ; java.math. BigInteger ; java.util. Observable ; java.util. Observer ; import import import import javax.swing. JButton ; javax.swing. JFrame ; javax.swing. JLabel ; javax.swing. JOptionPane ; 8 Übung zur Vorlesung Einführung in Software Engineering 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import import import import javax.swing. JPanel ; javax.swing. JScrollPane ; javax.swing. JTextArea ; javax.swing. JTextField ; import de.tud.se. cashregister .model. ReceiptPosition ; import de.tud.se. cashregister .model. Receipt ; public class CashRegisterFrame extends JFrame { /** * Generated UID. */ private static final long serialVersionUID = -3008711233508194058 L; private JTextArea receiptArea ; private JTextField quantityField ; private JTextField priceField ; private JTextField descriptionField ; private JButton addButton ; private Receipt receipt ; private Observer receiptObserver = new Observer () { @ Override public void update ( Observable o, Object arg) { updateShownReceipt (); } }; public CashRegisterFrame ( String title , Receipt receipt ) { // Initialize frame super(title); this. receipt = receipt ; this. receipt . addObserver ( receiptObserver ); setLocationByPlatform (true); setDefaultCloseOperation ( JFrame . DISPOSE_ON_CLOSE ); setLayout (new BorderLayout ()); // Create receipt area receiptArea = new JTextArea (20, 40); receiptArea . setEditable (false); add(new JScrollPane ( receiptArea ), BorderLayout . CENTER ); // Create fields JPanel fieldPanel = new JPanel (new GridBagLayout ()); add(fieldPanel , BorderLayout .SOUTH); GridBagConstraints c = new GridBagConstraints (); c. insets = new Insets (5, 5, 5, 5); c.fill = GridBagConstraints .BOTH; // Create quantity field and label quantityField = new JTextField (7); quantityField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel quantityLabel = new JLabel (" Quantity "); quantityLabel . setDisplayedMnemonic (’Q’); quantityLabel . setLabelFor ( quantityField ); fieldPanel .add( quantityLabel , c); fieldPanel .add( quantityField , c); // Create price field and label priceField = new JTextField (7); 9 Übung zur Vorlesung Einführung in Software Engineering 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 priceField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel priceLabel = new JLabel ("Price"); priceLabel . setDisplayedMnemonic (’P’); priceLabel . setLabelFor ( priceField ); fieldPanel .add(priceLabel , c); fieldPanel .add(priceField , c); // Create description field and label descriptionField = new JTextField (26); descriptionField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel descriptionLabel = new JLabel (" Description "); descriptionLabel . setDisplayedMnemonic (’D’); descriptionLabel . setLabelFor ( descriptionField ); fieldPanel .add( descriptionLabel , c); c. weightx = 1; fieldPanel .add( descriptionField , c); // Create add button addButton = new JButton ("Add"); addButton . setMnemonic (’A’); addButton . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { addReceiptPosition (); } }); c. weightx = 0; fieldPanel .add(addButton , c); // Optimize frame size pack (); // Show initial receipt updateShownReceipt (); // Set focus to initial control quantityField . requestFocus (); } @ Override public void dispose () { receipt . deleteObserver ( receiptObserver ); super. dispose (); } protected void clickOnAddButton () { addButton . doClick (); } protected void addReceiptPosition () { try { // Get values BigInteger quantity = new BigInteger ( quantityField . getText ()); BigDecimal price = new BigDecimal ( priceField . getText ()); String description = descriptionField . getText (); // Make sure that values are valid if ( quantity . compareTo ( BigInteger .ZERO) <= 0) { throw new IllegalStateException (" Quantity <= 0."); } if ( description == null || description .trim (). isEmpty ()) { throw new IllegalStateException (" Description not defined ."); 10 Übung zur Vorlesung Einführung in Software Engineering 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 } } // Add receipt position ReceiptPosition newPosition = new ReceiptPosition (quantity , price , description ); receipt . addPosition ( newPosition ); // Update UI controls quantityField . setText (null); priceField . setText (null); descriptionField . setText (null); // Update shown receipt updateShownReceipt (); // Update focus quantityField . requestFocus (); } catch ( Exception e) { // Show error message JOptionPane . showMessageDialog (this , e, " Error ", JOptionPane . ERROR_MESSAGE ); // Update focus quantityField . requestFocus (); } } protected void updateShownReceipt () { // Create new text to show in receipt area StringBuffer sb = new StringBuffer (); sb. append (" Quantity "); sb. append (’\t’); sb. append ("Price"); sb. append (’\t’); sb. append (" Description "); sb. append (’\t’); sb. append ("Sum"); sb. append (’\n’); for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append ("\n"); ReceiptPosition [] positions = receipt . getPositions (); for ( ReceiptPosition position : positions ) { sb. append ( position . getQuantity ()); sb. append (’\t’); sb. append ( position . getPrice ()); sb. append (’\t’); sb. append ( position . getDescription ()); sb. append (’\t’); sb. append ( position . computePositionSum ()); sb. append (’\n’); } for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append (’\n’); sb. append (’\t’); sb. append (’\t’); sb. append (’\t’); sb. append ( receipt . computeReceiptSum ()); // Update shown text receiptArea . setText (sb. toString ()); } 11 Übung zur Vorlesung Einführung in Software Engineering Lösung b (Variante Listener): Diese Lösung definiert einen eigenen Beobachter mit zugehörigem Ereignis-Objekt, welches die gängige Praxis zum Informieren von Beobachtern über Ereignisse in Java-Anwendungen ist. Die Verwendung empfiehlt sich insbesondere bei komplexen Zustandsänderungen oder um über spezielle Ereignisse während der Programmausführung zu informieren. Listener und Ereignis-Objekt definieren: Listing 8: IReceiptListener 1 2 3 4 5 6 7 package de.tud.se. cashregister .model.event; import java.util. EventListener ; public interface IReceiptListener extends EventListener { public void receiptPositionAdded ( ReceiptEvent e); } Listing 9: ReceiptEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package de.tud.se. cashregister .model.event; import java.util. EventObject ; import de.tud.se. cashregister .model. ReceiptPosition ; import de.tud.se. cashregister .model. Receipt ; public class ReceiptEvent extends EventObject { /** * Generated UID. */ private static final long serialVersionUID = 6825058086314410795 L; private ReceiptPosition addedPosition ; public ReceiptEvent ( Receipt source , ReceiptPosition addedPosition ) { super( source ); this. addedPosition = addedPosition ; } @ Override public Receipt getSource () { return ( Receipt )super. getSource (); } public ReceiptPosition getAddedPosition () { return addedPosition ; } } Über Zustandsänderungen im Subjekt informieren: Listing 10: Receipt 1 2 3 4 5 6 7 8 9 10 11 12 package de.tud.se. cashregister .model; import java.math. BigDecimal ; import java.util. LinkedList ; import java.util.List; import javax.swing.event. EventListenerList ; import de.tud.se. cashregister .model.event. ReceiptEvent ; import de.tud.se. cashregister .model.event. IReceiptListener ; public class Receipt { 12 Übung zur Vorlesung Einführung in Software Engineering 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 } private EventListenerList listeners = new EventListenerList (); private List < ReceiptPosition > positions = new LinkedList < ReceiptPosition >(); public void addPosition ( ReceiptPosition position ) { if ( positions .add( position )) { fireReceiptPositionAdded (new ReceiptEvent (this , position )); } } public ReceiptPosition [] getPositions () { return positions . toArray (new ReceiptPosition [ positions .size () ]); } public void addReceiptListener ( IReceiptListener listener ) { if ( listener != null) { listeners .add( IReceiptListener .class , listener ); } } public void removeReceiptListener ( IReceiptListener listener ) { if ( listener != null) { listeners . remove ( IReceiptListener .class , listener ); } } protected void fireReceiptPositionAdded ( ReceiptEvent e) { IReceiptListener [] toInform = listeners . getListeners ( IReceiptListener . class ); for ( IReceiptListener l : toInform ) { l. receiptPositionAdded (e); } } public BigDecimal computeReceiptSum () { BigDecimal totalSum = BigDecimal .ZERO; for ( ReceiptPosition position : positions ) { totalSum = totalSum .add( position . computePositionSum ()); } return totalSum ; } Auf Zustandsänderungen im Subjekt reagieren: Listing 11: CashRegisterFrame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package de.tud.se. cashregister .ui; import import import import import import import import java.awt. BorderLayout ; java.awt. GridBagConstraints ; java.awt. GridBagLayout ; java.awt. Insets ; java.awt.event. ActionEvent ; java.awt.event. ActionListener ; java.math. BigDecimal ; java.math. BigInteger ; import import import import import import import import javax.swing. JButton ; javax.swing. JFrame ; javax.swing. JLabel ; javax.swing. JOptionPane ; javax.swing. JPanel ; javax.swing. JScrollPane ; javax.swing. JTextArea ; javax.swing. JTextField ; 13 Übung zur Vorlesung Einführung in Software Engineering 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import import import import de.tud.se. cashregister .model. ReceiptPosition ; de.tud.se. cashregister .model. Receipt ; de.tud.se. cashregister .model.event. IReceiptListener ; de.tud.se. cashregister .model.event. ReceiptEvent ; public class CashRegisterFrame extends JFrame { /** * Generated UID. */ private static final long serialVersionUID = -3008711233508194058 L; private JTextArea receiptArea ; private JTextField quantityField ; private JTextField priceField ; private JTextField descriptionField ; private JButton addButton ; private Receipt receipt ; private IReceiptListener receiptListener = new IReceiptListener () { @ Override public void receiptPositionAdded ( ReceiptEvent e) { updateShownReceipt (); } }; public CashRegisterFrame ( String title , Receipt receipt ) { // Initialize frame super(title); this. receipt = receipt ; this. receipt . addReceiptListener ( receiptListener ); setLocationByPlatform (true); setDefaultCloseOperation ( JFrame . DISPOSE_ON_CLOSE ); setLayout (new BorderLayout ()); // Create receipt area receiptArea = new JTextArea (20, 40); receiptArea . setEditable (false); add(new JScrollPane ( receiptArea ), BorderLayout . CENTER ); // Create fields JPanel fieldPanel = new JPanel (new GridBagLayout ()); add(fieldPanel , BorderLayout .SOUTH); GridBagConstraints c = new GridBagConstraints (); c. insets = new Insets (5, 5, 5, 5); c.fill = GridBagConstraints .BOTH; // Create quantity field and label quantityField = new JTextField (7); quantityField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel quantityLabel = new JLabel (" Quantity "); quantityLabel . setDisplayedMnemonic (’Q’); quantityLabel . setLabelFor ( quantityField ); fieldPanel .add( quantityLabel , c); fieldPanel .add( quantityField , c); // Create price field and label priceField = new JTextField (7); priceField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { 14 Übung zur Vorlesung Einführung in Software Engineering 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 clickOnAddButton (); } }); JLabel priceLabel = new JLabel ("Price "); priceLabel . setDisplayedMnemonic (’P’); priceLabel . setLabelFor ( priceField ); fieldPanel .add(priceLabel , c); fieldPanel .add(priceField , c); // Create description field and label descriptionField = new JTextField (26); descriptionField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel descriptionLabel = new JLabel (" Description "); descriptionLabel . setDisplayedMnemonic (’D’); descriptionLabel . setLabelFor ( descriptionField ); fieldPanel .add( descriptionLabel , c); c. weightx = 1; fieldPanel .add( descriptionField , c); // Create add button addButton = new JButton ("Add"); addButton . setMnemonic (’A’); addButton . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { addReceiptPosition (); } }); c. weightx = 0; fieldPanel .add(addButton , c); // Optimize frame size pack (); // Show initial receipt updateShownReceipt (); // Set focus to initial control quantityField . requestFocus (); } @ Override public void dispose () { receipt . removeReceiptListener ( receiptListener ); super. dispose (); } protected void clickOnAddButton () { addButton . doClick (); } protected void addReceiptPosition () { try { // Get values BigInteger quantity = new BigInteger ( quantityField . getText ()); BigDecimal price = new BigDecimal ( priceField . getText ()); String description = descriptionField . getText (); // Make sure that values are valid if ( quantity . compareTo ( BigInteger .ZERO) <= 0) { throw new IllegalStateException (" Quantity <= 0."); } if ( description == null || description .trim (). isEmpty ()) { throw new IllegalStateException (" Description not defined ."); } // Add receipt position ReceiptPosition newPosition = new ReceiptPosition (quantity , price , description ); 15 Übung zur Vorlesung Einführung in Software Engineering 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 } 16 receipt . addPosition ( newPosition ); // Update UI controls quantityField . setText (null); priceField . setText (null); descriptionField . setText (null); // Update shown receipt updateShownReceipt (); // Update focus quantityField . requestFocus (); } catch ( Exception e) { // Show error message JOptionPane . showMessageDialog (this , e, " Error ", JOptionPane . ERROR_MESSAGE ); // Update focus quantityField . requestFocus (); } } protected void updateShownReceipt () { // Create new text to show in receipt area StringBuffer sb = new StringBuffer (); sb. append (" Quantity "); sb. append (’\t’); sb. append ("Price"); sb. append (’\t’); sb. append (" Description "); sb. append (’\t’); sb. append ("Sum"); sb. append (’\n’); for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append ("\n"); ReceiptPosition [] positions = receipt . getPositions (); for ( ReceiptPosition position : positions ) { sb. append ( position . getQuantity ()); sb. append (’\t’); sb. append ( position . getPrice ()); sb. append (’\t’); sb. append ( position . getDescription ()); sb. append (’\t’); sb. append ( position . computePositionSum ()); sb. append (’\n’); } for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append (’\n’); sb. append (’\t’); sb. append (’\t’); sb. append (’\t’); sb. append ( receipt . computeReceiptSum ()); // Update shown text receiptArea . setText (sb. toString ()); } Übung zur Vorlesung Einführung in Software Engineering Lösung b (Variante Bean): Diese Lösung entspricht der Listener-Variante, jedoch wird der von der Java-API vorgegebene Beobachter java.beans.PropertyChangeListener mit zugehörigem Ereignis-Objekt java.beans.PropertyChangeEvent verwendet. Diese sollten verwendet werden, um über Änderungen von Instanzvariablen eines Objekts (Eigenschaften) zu informieren. Vorteilhaft ist neben der einheitlichen API, dass auf dem Reflection-Framework basierenden Technologien häufig mit diesen Klassen arbeiten und so eine Kompatibilität gegeben ist. Auch für Instanzvariablen mit einem Index, wie Arrays oder Listen, kann dieser Ansatz verwendet werden, um über die Änderung des Wertes an einem definierten Index zu informieren. Dies ist in dieser Aufgabenstellung gegeben, womit diese Variante aufgrund der genannte Vorteile als Lösung zu bevorzugen ist. Über Zustandsänderungen im Subjekt informieren: Listing 12: Receipt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package de.tud.se. cashregister .model; import import import import java.math. BigDecimal ; java.util. LinkedList ; java.util.List; java.util. Observable ; public class Receipt extends Observable { private List < ReceiptPosition > positions = new LinkedList < ReceiptPosition >(); public void addPosition ( ReceiptPosition position ) { if ( positions .add( position )) { setChanged (); notifyObservers ( position ); // Resets changed flag via clearChanged () } } public ReceiptPosition [] getPositions () { return positions . toArray (new ReceiptPosition [ positions .size () ]); } public BigDecimal computeReceiptSum () { BigDecimal totalSum = BigDecimal .ZERO; for ( ReceiptPosition position : positions ) { totalSum = totalSum .add( position . computePositionSum ()); } return totalSum ; } } Auf Zustandsänderungen im Subjekt reagieren: Listing 13: CashRegisterFrame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package de.tud.se. cashregister .ui; import import import import import import import import import import java.awt. BorderLayout ; java.awt. GridBagConstraints ; java.awt. GridBagLayout ; java.awt. Insets ; java.awt.event. ActionEvent ; java.awt.event. ActionListener ; java.math. BigDecimal ; java.math. BigInteger ; java.util. Observable ; java.util. Observer ; import javax.swing. JButton ; import javax.swing. JFrame ; import javax.swing. JLabel ; 17 Übung zur Vorlesung Einführung in Software Engineering 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import import import import import javax.swing. JOptionPane ; javax.swing. JPanel ; javax.swing. JScrollPane ; javax.swing. JTextArea ; javax.swing. JTextField ; import de.tud.se. cashregister .model. ReceiptPosition ; import de.tud.se. cashregister .model. Receipt ; public class CashRegisterFrame extends JFrame { /** * Generated UID. */ private static final long serialVersionUID = -3008711233508194058 L; private JTextArea receiptArea ; private JTextField quantityField ; private JTextField priceField ; private JTextField descriptionField ; private JButton addButton ; private Receipt receipt ; private Observer receiptObserver = new Observer () { @ Override public void update ( Observable o, Object arg) { updateShownReceipt (); } }; public CashRegisterFrame ( String title , Receipt receipt ) { // Initialize frame super(title); this. receipt = receipt ; this. receipt . addObserver ( receiptObserver ); setLocationByPlatform (true); setDefaultCloseOperation ( JFrame . DISPOSE_ON_CLOSE ); setLayout (new BorderLayout ()); // Create receipt area receiptArea = new JTextArea (20, 40); receiptArea . setEditable (false); add(new JScrollPane ( receiptArea ), BorderLayout . CENTER ); // Create fields JPanel fieldPanel = new JPanel (new GridBagLayout ()); add(fieldPanel , BorderLayout .SOUTH); GridBagConstraints c = new GridBagConstraints (); c. insets = new Insets (5, 5, 5, 5); c.fill = GridBagConstraints .BOTH; // Create quantity field and label quantityField = new JTextField (7); quantityField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel quantityLabel = new JLabel (" Quantity "); quantityLabel . setDisplayedMnemonic (’Q’); quantityLabel . setLabelFor ( quantityField ); fieldPanel .add( quantityLabel , c); fieldPanel .add( quantityField , c); // Create price field and label 18 Übung zur Vorlesung Einführung in Software Engineering 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 priceField = new JTextField (7); priceField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel priceLabel = new JLabel ("Price "); priceLabel . setDisplayedMnemonic (’P’); priceLabel . setLabelFor ( priceField ); fieldPanel .add(priceLabel , c); fieldPanel .add(priceField , c); // Create description field and label descriptionField = new JTextField (26); descriptionField . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { clickOnAddButton (); } }); JLabel descriptionLabel = new JLabel (" Description "); descriptionLabel . setDisplayedMnemonic (’D’); descriptionLabel . setLabelFor ( descriptionField ); fieldPanel .add( descriptionLabel , c); c. weightx = 1; fieldPanel .add( descriptionField , c); // Create add button addButton = new JButton ("Add"); addButton . setMnemonic (’A’); addButton . addActionListener (new ActionListener () { @ Override public void actionPerformed ( ActionEvent e) { addReceiptPosition (); } }); c. weightx = 0; fieldPanel .add(addButton , c); // Optimize frame size pack (); // Show initial receipt updateShownReceipt (); // Set focus to initial control quantityField . requestFocus (); } @ Override public void dispose () { receipt . deleteObserver ( receiptObserver ); super. dispose (); } protected void clickOnAddButton () { addButton . doClick (); } protected void addReceiptPosition () { try { // Get values BigInteger quantity = new BigInteger ( quantityField . getText ()); BigDecimal price = new BigDecimal ( priceField . getText ()); String description = descriptionField . getText (); // Make sure that values are valid if ( quantity . compareTo ( BigInteger .ZERO) <= 0) { throw new IllegalStateException (" Quantity <= 0."); } if ( description == null || description .trim (). isEmpty ()) { 19 Übung zur Vorlesung Einführung in Software Engineering 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 } 20 throw new IllegalStateException (" Description not defined ."); } // Add receipt position ReceiptPosition newPosition = new ReceiptPosition (quantity , price , description ); receipt . addPosition ( newPosition ); // Update UI controls quantityField . setText (null); priceField . setText (null); descriptionField . setText (null); // Update shown receipt updateShownReceipt (); // Update focus quantityField . requestFocus (); } catch ( Exception e) { // Show error message JOptionPane . showMessageDialog (this , e, " Error ", JOptionPane . ERROR_MESSAGE ); // Update focus quantityField . requestFocus (); } } protected void updateShownReceipt () { // Create new text to show in receipt area StringBuffer sb = new StringBuffer (); sb. append (" Quantity "); sb. append (’\t’); sb. append ("Price"); sb. append (’\t’); sb. append (" Description "); sb. append (’\t’); sb. append ("Sum"); sb. append (’\n’); for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append ("\n"); ReceiptPosition [] positions = receipt . getPositions (); for ( ReceiptPosition position : positions ) { sb. append ( position . getQuantity ()); sb. append (’\t’); sb. append ( position . getPrice ()); sb. append (’\t’); sb. append ( position . getDescription ()); sb. append (’\t’); sb. append ( position . computePositionSum ()); sb. append (’\n’); } for (int i = 0; i < 50; i++) { sb. append (’=’); } sb. append (’\n’); sb. append (’\t’); sb. append (’\t’); sb. append (’\t’); sb. append ( receipt . computeReceiptSum ()); // Update shown text receiptArea . setText (sb. toString ()); } Übung zur Vorlesung Einführung in Software Engineering Aufgabe 9.2 (2 Punkte) Ziel: Angewendete Entwurfsmuster erkennen. Aufgabe: Im Folgenden sind Ausschnitte für die Klassen AbstractListModel und JFileChooser aus der Java 6 API Spezifikation, welche unter der Adresse http://docs.oracle.com/javase/6/docs/api/ verfügbar ist, abgebildet. Nennen Sie zunächst das auf dem Ausschnitt, unter Berücksichtigung der auf dem Aufgabenzettel genannten Informationen, erkennbare Entwurfsmuster und ordnen Sie anschließend die Elemente der Java API den im Folgenden aufgelisteten Bestandteilen (Participants) des Entwurfmusters zu. Hierzu ist es ggf. erforderlich, die vollständige Spezifikation der Klasse und weiterer verwendeter Typen zu betrachten. Nennen Sie genau ein Beispiel je Bestandteil! Reichen Sie Ihre Lösung per SVN als eine PDF-Datei ein. Diese muss unter „trunk/ex09/solutions.pdf“ abgelegt werden. Bestandteile der Entwurfsmuster: • Strategy Design Pattern: – Ein oder mehrere Kontexte – Strategie – Ein oder mehrere Methoden in der Strategie, welche von den Kontexten genutzt werden – Ein oder mehrere konkrete Strategien • Observer Design Pattern: – Optional Subjekt – Methoden zum Verwalten von Beobachtern – Methode zum Informieren der Beobachter über Zustandsänderungen/Ereignisse – Konkretes Subjekt – Methoden des konkreten Subjekts, welche die Methoden zum Informieren der Beobachter nutzen (In dieser Aufgabe nicht erforderlich, da nicht direkt aus Java API Spezifikation und den gegebenen Informationen erkennbar) – Abstrakte Beobachter-Definition – Konkrete Beobachter 21 Übung zur Vorlesung Einführung in Software Engineering Ausschnitte aus Klassen der Java 6 API Spezifikation: a) Class javax.swing.AbstractListModel Definiert die Grundfunktionalität von Listen, wie sie in Benutzeroberflächenkomponenten von Swing, wie z.B. der JComboBox, verwendet werden. b) Class javax.swing.JFileChooser extends javax.swing.JComponent In Swing implementierter Dialog zum auswählen von Dateien oder Verzeichnissen. 22 Übung zur Vorlesung Einführung in Software Engineering Lösung: a) Observer Design Pattern • Subjekt: AbstractListModel • Methoden zum Verwalten von Beobachtern: addListDataListener(ListDataListener), getListDataListeners(), removeListDataListener(ListDataListener) • Methode zum informieren der Beobachter über Zustandsänderungen/Ereignisse fireContentsChanged(. . .), fireIntervalAdded(. . .), fireIntervalRemoved(. . .) • Konkretes Subjekt: BasicDirectoryModel, DefaultComboBoxModel, DefaultListModel, MetalFileChooserUI.DirectoryComboBoxModel, MetalFileChooserUI.FilterComboBoxModel • Methoden des konkreten Subjekts welche die Methoden zum Informieren der Beobachter nutzen DefaultListModel#add(. . .), . . . • Abstrakte Beobachter-Definition: ListDataListener • Konkrete Beobachter: JComboBox b) Strategy Design Pattern • Ein oder mehrere Kontexte: JFileChooser • Abstrakte Strategie: FileFilter • Ein oder mehrere Methoden in der abstrakten Strategie welche von den Kontexten genutzt werden: accept(File) • Ein oder mehrere konkrete Strategien: BasicFileChooserUI.AcceptAllFileFilter, FileNameExtensionFilter 23