softwareproduktlinien - laufzeitvariabilitätobserver pattern 29 “ define [s] a one-to-many...
TRANSCRIPT
Softwareproduktlinien - Laufzeitvariabilität
Christian Kästner (Universität Marburg) Sven Apel (Universität Passau)
Gunter Saake, Thomas Thüm (Universität Magdeburg)
1
Dom
ain
Eng.
Ap
plic
atio
n En
g.
Feature-Auswahl
Feature-Modell Wiederverwendbare Implementierungs- artefakte
Generator Fertiges Program 2
Wie Variabilität implementieren?
Agenda
3
Graph-Beispiel Variabilität mit Laufzeitparametern Wiederholung: Modularität Design Patterns für Variabilität Grenzen herkömmlicher Techniken
Ein Beispiel
4
Beispiel: Graph-Bibliothek Durchgängiges Beispiel in Vorlesung
(Chat-Client in der Übung) Bibliothek von Graph-Datenstrukturen und –Algorithmen Gewichtete/ungewichtete Kanten Gerichtete/ungerichtete Kanten Gefärbte Knoten Algorithmen: kürzester Pfad, minimale Spannbäume, Transitive
Hülle, …
5
Graph-Feature-Modell
6
Beispiel: Graph-Implementierung
class Edge { Node a, b; Color color = new Color(); Weight weight = new Weight(); Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { Color.setDisplayColor(color); a.print(); b.print(); weight.print(); } }
class Edge { Node a, b; Color color = new Color(); Weight weight = new Weight(); Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { Color.setDisplayColor(color); a.print(); b.print(); weight.print(); } }
class Edge { Node a, b; Color color = new Color(); Weight weight Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { Color.setDisplayColor(color); a.print(); b.print(); weight.print(); } }
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
class Node { int id = 0; Color color = new Color(); void print() { Color.setDisplayColor(color); System.out.print(id); } }
class Node { int id = 0; Color color = new Color(); void print() { Color.setDisplayColor(color); System.out.print(id); } }
class Color { static void setDisplayColor(Color c) { ... } } class Weight { void print() { ... } }
7
Laufzeitparameter
8
Parameter
9
Parameter –i in grep
10
Globale Konfigurationsoptionen class Conf { public static boolean Logging = false; public static boolean Windows = false; public static boolean Linux = true; } class Main { public void foo() { if (Conf.Logging) log(„running foo()“); if (Conf.Windows) callWindowsMethod(); else if (Conf.Linux) callLinuxMethod(); else throw RuntimeException(); } }
11
Graph-Implementierung
class Edge { Node a, b; Color color = new Color(); Weight weight = new Weight(); Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { if (Conf. COLORED) Color.setDisplayColor(color); a.print(); b.print(); if (Conf.WEIGHTED) weight.print(); } }
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); if (Conf.WEIGHTED) e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) if (!Conf.WEIGHTED) throw RuntimeException(); Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
class Node { int id = 0; Color color = new Color(); void print() { if (Conf.COLORED) Color.setDisplayColor(color); System.out.print(id); } }
class Color { static void setDisplayColor(Color c) { ... } } class Weight { void print() { ... } }
class Conf { public static boolean COLORED = true; public static boolean WEIGHTED = false; }
12
Propagierte Parameter
13
durch viele Aufrufe propagiert statt globaler Variable
Konfiguration
14
Kommandozeilenparameter Config-Datei Dialog Quelltext …
Diskussion
15
Variabilität im gesamten Program verteilt Globale Variablen oder lange Parameterlisten
Konfiguration geprüft? Änderungen zur Laufzeit möglich? Geschützt vor Aufruf deaktivierter Funktionalität?
Kein Generator; immer alle Variabilität ausgeliefert Codegröße, Speicherverbrauch, Performance Ungenutzte Funktionalität als Risiko
Dom
ain
Eng.
Ap
plic
atio
n En
g.
16
Parameterauswahl (Feature-Auswahl)
Parameterliste (Feature-Modell) Program mit
Laufzeitparametern
Setzen der Startparameter Programmausführung mit gewünschtem Verhalten
Wiederholung: Modularität
17
Was ist Modularität?
18
Modularität ist Kohäsion plus Kapselung Kapselung: Verstecken von Implementierungsdetails
hinter einer Schnittstelle Kohäsion: Gruppieren verwandter Programmkonstrukte
in einer adressierbaren Einheit (z.B. Paket, Klasse, …) Kohäsive und schwach gekoppelte Module können
isoliert verstanden werden Reduziert die Komplexität des
Softwareentwicklungsprozesses
Kapselung
Implementierungsdetails werden versteckt
Schnittstelle beschreibt Verhalten
Implementierung wird austauschbar
public class ArrayList<E> { public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } ……. }
public interface List<E> { void add(int index, E element); int indexOf(Object o); …. }
19
Kohäsion/Kopplung – Beispiel
Gruppierung von Methoden/Aufgaben Viele Aufrufe über Grenzen der Gruppe Gruppe implementiert verschiedene Belange
20
Warum Modularität?
21
Software leicht verständlich (divide and conquer) Versteckt Komplexität von Teilstücken hinter
Schnittstellen (information hiding) Einfacher zu warten, da Änderungen lokal erfolgen
können (maintainability) Teile der Software können wiederverwendet werden
(reusability) Module können auch in neuem Kontext in anderen
Projekte neu zusammengestellt werden (variability)
Zerlegung und Wiederverwendung
22
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); if (Conf.WEIGHTED) e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) if (!Conf.WEIGHTED) throw RuntimeException(); Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
class Node { int id = 0; Color color = new Color(); void print() { if (Conf.COLORED) Color.setDisplayColor(color); System.out.print(id); } }
Probleme? – Verstreuter Code
Code Scattering
class Edge { Node a, b; Color color = new Color(); Weight weight; Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { if (Conf. COLORED) Color.setDisplayColor(color); a.print(); b.print(); if (!Conf.WEIGHTED) weight.print(); } }
class Color { static void setDisplayColor(Color c) { ... } } class Weight { void print() { ... } }
23
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); if (Conf.WEIGHTED) e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) if (!Conf.WEIGHTED) throw RuntimeException(); Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
Probleme? – Vermischter Code
Code Tangling
class Edge { Node a, b; Color color = new Color(); Weight weight; Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { if (Conf. COLORED) Color.setDisplayColor(color); a.print(); b.print(); if (!Conf.WEIGHTED) weight.print(); } }
class Node { int id = 0; Color color = new Color(); void print() { if (Conf.COLORED) Color.setDisplayColor(color); System.out.print(id); } }
class Color { static void setDisplayColor(Color c) { ... } } class Weight { void print() { ... } }
24
class Graph { Vector nv = new Vector(); Vector ev = new Vector(); Edge add(Node n, Node m) { Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); if (Conf.WEIGHTED) e.weight = new Weight(); return e; } Edge add(Node n, Node m, Weight w) if (!Conf.WEIGHTED) throw RuntimeException(); Edge e = new Edge(n, m); nv.add(n); nv.add(m); ev.add(e); e.weight = w; return e; } void print() { for(int i = 0; i < ev.size(); i++) { ((Edge)ev.get(i)).print(); } } }
Probleme? – Replizierter Code
Code Replication class Edge { Node a, b; Color color = new Color(); Weight weight; Edge(Node _a, Node _b) { a = _a; b = _b; } void print() { if (Conf. COLORED) Color.setDisplayColor(color); a.print(); b.print(); if (!Conf.WEIGHTED) weight.print(); } }
class Node { int id = 0; Color color = new Color(); void print() { if (Conf.COLORED) Color.setDisplayColor(color); System.out.print(id); } }
class Color { static void setDisplayColor(Color c) { ... } } class Weight { void print() { ... } }
25
Design Patterns für Variabilität
26
Design Patterns
27
Muster für den Entwurf von Lösungen für wiederkehrende Probleme
Viele Design Patterns für Variabilität, Entkoppelung und Erweiterbarkeit
Hier Auswahl: Observer Template Method Strategy Decorator
Observer Pattern
28
VIEW
A = 50%
B = 30%
C = 20%
Obs
erve
rs
Subj
ect
[Quelle: Meyer/Bay]
Observer Pattern
29
“Define[s] a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.” [GoF, p 293]
Observer
Subject
3) Informiert über Event
1) Bekundet Interesse
2) Merke Interessenten
29
• Interfaces fügen zusätzliche Flexibilität hinzu • In Implementierung
• Klasse für Observer • Klasse für Subject • Liste in Subject für Observer • Subject.addToObservers(Observer) (wird vom Observer aufgerufen) • Observer.notify() (wird von Subject aufgerufen)
30
public abstract class BubbleSorter{ protected int length = 0; protected void sort() { if (length <= 1) return; for (int nextToLast= length-2; nextToLast>= 0; nextToLast--) for (int index = 0; index <= nextToLast; index++) if (outOfOrder(index)) swap(index); } protected abstract void swap(int index); protected abstract boolean outOfOrder(int index); }
Template Method Pattern
IntBubbleSorter
31
public class IntBubbleSorter extends BubbleSorter{ private int[] array = null; public void sort(int[] theArray) { array = theArray; length = array.length; super.sort(); } protected void swap(int index) { int temp = array[index]; array[index] = array[index+ 1]; array[index+1] = temp; } protected boolean outOfOrder(int index) { return (array[index] > array[index+ 1]); } }
Strategy Pattern
32
Strategy Pattern: Beispiel
33
Problem: Unflexible Erweiterungsmechanismen
34
Unflexible Erweiterungsmechanismen Subklassen für Erweiterungen: modular, aber unflexibel Kein “mix & match”
Stack
SecureStack
SynchronizedStack
UndoStack
Stack
SecureStack SynchronizedStackUndoStack
35 z. B. White-Box-Framework
Lösung I Kombinierte Klassenhierarchien Kombinatorische Explosion der Varianten Massive Code-Replikation
Mehrfachvererbung Kombinatorische Explosion Aufgrund diverser Probleme
(u. a. Diamant-Problem) in nur wenigen Sprachen verfügbar
36
Stack
SecureStack
SynchronizedUndoSecureStac
UndoStack
UndoSecureStackSynchronizedUndoStack
SynchronizedStack
Diamant-Problem
Was passiert? new LockedUndoStack().pop()
+push()+pop()+size()
-valuesStack
+push()+pop()+size()+lock()+unlock()
LockedStack
+push()+pop()+undo()
-logUndoStack
LockedUndoStack
“Multiple inheritance is good, but there is no good way to do it.”
A. SYNDER
37
Delegation statt Vererbung
class LockedStack implements IStack { final IStack _delegate; public LockedStack(IStack delegate) { this._delegate = delegate; } private void lock() { /* ... /* } private void unlock() { /* ... /* } public void push(Object o) { lock(); _delegate.push(o); unlock(); } public Object pop() { lock(); Object result = _delegate.pop(); unlock(); return result; } public int size() { return _delegate.size(); } }
class UndoStack implements IStack { final IStack _delegate; public UndoStack(IStack delegate) { this._delegate = delegate; } public void undo() { /* ... /* } public void push(Object o) { remember(); _delegate.push(o); } public Object pop() { remember(); return _delegate.pop(); } public int size() { return _delegate.size(); } }
38
Main: IStack stack = new UndoStack( new LockedStack(new Stack()));
Decorator Pattern
+push()+pop()+size()
«interface»IStack
+push()+pop()+size()
-valuesStack
+push()+pop()+size()
-delegateStackDecorator
+push()+pop()+size()+lock()+unlock()
LockedStack
+push()+pop()+undo()
-logUndoStack
+push()+pop()+encrypt()+decrypt()
-keyphrase
SecureStack
1
1
39
Beispiel: Decorator in java.io
40
java.io enthält verschiedene Funktionen zur Ein- und Ausgabe: Programme operieren auf Stream-Objekten ... Unabhängig von der Datenquelle/-ziel und der Art der Daten
Delegation statt Vererbung – Diskussion Dynamische Kombination möglich Erweiterungen müssen alle unabhängig sein Kann keine Methoden hinzufügen, nur bestehende
erweitern Kein spätes Binden (keine virtuellen Methoden) Viele Indirektionen in der Ausführung (Performance) Mehrere Objektinstanzen bilden ein Objekt
(Objektschizophrenie)
41
Flexible Erweiterungsmechanismen?
??? 42
Ausblick
43
Konfiguration mit Generator zur Übersetzungszeit Flexiblere Erweiterungsmechanismen Modularisierung von Features
Literatur Gamma, Erich; Richard Helm, Ralph Johnson, and John
Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2. [Standardwerk Design Patterns]
Bertrand Meyer, Object-Oriented Software Construction, Prentice Hall, 1997 – Chapters 3, 4 [Zu Modularität]
44