exercise sheet with solution

Werbung
Ü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
Herunterladen