design-patterns-composite.html 6.92 KB
Newer Older
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
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
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
<p>Das Kompositum (engl. <i>Composite</i>) ist ein Strukturmuster, das angewendet wird, um Objekte, die sich ein gemeinsames Interface teilen, zu einer hierarchischer Baumstruktur zusammenfügen. Die grundlegende Idee des Kompositum-Musters ist es, sowohl primitive Objekte als auch zusammengesetzte Objekte, die aus mehreren primitiven Objekte bestehen, in einem Interface zu repräsentieren. Dadurch können einzelne Objekte und ihre Kompositionen einheitlich behandelt werden.</p>

<p>Es soll also folgende Frage durch das Muster beantwortet werden: Wie können in einer objektorientierten Sprache einzelne Objekte als Teile (= Komponenten) geschickt zu einem Ganzen (= Kompositum) zusammengesetzt werden?</p>

<p>Die Herleitung des Musters soll wieder über ein Beispiel erfolgen. Es liegt die folgende Implementierung vor, um die 3 grundlegenden Formen <code>Line</code>, <code>Circle</code> und <code>Text</code> grafisch darzustellen. Aus diesen primitiven Formen sollen komplexe, zusammengesetzte Formen erzeugt werden können.</p>

<ul class="nav nav-tabs" id="composite-tabs" role="tablist">
  <li class="nav-item"><a href="#composite-tabs-line" class="nav-link active" data-toggle="tab" role="tab">Line</a></li>
  <li class="nav-item"><a href="#composite-tabs-circle" class="nav-link" data-toggle="tab" role="tab">Circle</a></li>
  <li class="nav-item"><a href="#composite-tabs-text" class="nav-link" data-toggle="tab" role="tab">Text</a></li>
</ul>
<div class="tab-content" id="composite-tabs-content">
  <div class="tab-pane show active" id="composite-tabs-line" role="tabpanel">
	<pre><code class="language-java line-numbers">class Line {
    GraphicsContext gc;
    Point2D a, b;
    void draw() { gc.strokeLine(a.getX(), a.getY(), b.getX(), b.getY()); }
}</code></pre>  
  </div>
  <div class="tab-pane" id="composite-tabs-circle" role="tabpanel">
	<pre><code class="language-java line-numbers">class Circle {
    GraphicsContext gc;
    Point2D a;
    Double radius;
    void draw() { gc.strokeOval(a.getX(), a.getY(), radius, radius); }
}</code></pre> 
  </div>
  <div class="tab-pane" id="composite-tabs-text" role="tabpanel">
	<pre><code class="language-java line-numbers">class Text {
    GraphicsContext gc;
    Point2D a;
    String value;
    void draw() { gc.fillText(value, a.getX(), a.getY()); }
}</code></pre> 
  </div>
</div>

<p>Es bietet sich an, ein gemeinsames Interface für die Formen zu spezifieren, das die Methode <code>draw</code> für jede Form vorschreibt. Da sich alle Formen auch ein gemeinsames Attribut vom Typ <code>GraphicsContext</code> teilen und ein Interface zustandslos ist, wird eine abstrakte Klasse <code>Shape</code> eingeführt.</p>

<ul class="nav nav-tabs" id="composite-tabs2" role="tablist">
  <li class="nav-item"><a href="#composite-tabs-shape" class="nav-link active" data-toggle="tab" role="tab">Shape</a></li>  
  <li class="nav-item"><a href="#composite-tabs-line2" class="nav-link" data-toggle="tab" role="tab">Line</a></li>
  <li class="nav-item"><a href="#composite-tabs-circle2" class="nav-link" data-toggle="tab" role="tab">Circle</a></li>
  <li class="nav-item"><a href="#composite-tabs-text2" class="nav-link" data-toggle="tab" role="tab">Text</a></li>
</ul>
<div class="tab-content" id="composite-tabs2-content">
  <div class="tab-pane show active" id="composite-tabs-shape" role="tabpanel">
	<pre><code class="language-java line-numbers">abstract class Shape {
    GraphicsContext gc;
    abstract void draw();
}</code></pre>  
  </div>
  <div class="tab-pane show" id="composite-tabs-line2" role="tabpanel">
	<pre><code class="language-java line-numbers">class Line extends Shape {
    Point2D a, b;

    @Override
    void draw() { gc.strokeLine(a.getX(), a.getY(), b.getX(), b.getY()); }
}</code></pre>  
  </div>
  <div class="tab-pane" id="composite-tabs-circle2" role="tabpanel">
	<pre><code class="language-java line-numbers">class Circle extends Shape {
    Point2D a;
    Double radius;

    @Override
    void draw() { gc.strokeOval(a.getX(), a.getY(), radius, radius); }
}</code></pre> 
  </div>
  <div class="tab-pane" id="composite-tabs-text2" role="tabpanel">
	<pre><code class="language-java line-numbers">class Text extends Shape {
    Point2D a;
    String value;

    @Override
    void draw() { gc.strokeText(value, a.getX(), a.getY()); }
}</code></pre> 
  </div>
</div>

<p>Das eigentliche Kompositum ist eine zusammengesetzte Form, die ihrerseits aus den anderen bisher zur Verfügung stehenden Formen besteht.</p>
<pre><code class="language-java line-numbers">class CompositeShape extends Shape {
    Set&lt;Shape&gt; components = new HashSet<>();
	
    @Override
    void draw() { components.forEach(c -> c.draw()); }
}
</code></pre>

<p>Beispiele für zusammengesetzte Formen sind das folgend dargestellte Rechteck, das aus 4 Linien besteht, und das Strichmännchen, das aus einem Kreis und 4 Linien besteht.</p>
<img src="media/patterns_composite_shapes.png" style="width:750px">
<label>Zusammengesetzte Formen</label>

<p>Dadurch dass das Kompositum als Teile nicht nur primitive Formen sondern auch andere zusammengesetzte Formen aufnehmen kann, entsteht eine Teil-Ganzes-Hierarchie, die in ihrer Tiefe grundsätzlich nicht beschränkt ist. Die Beziehungen der beteiligten Objekte stellen eine Baumstruktur dar, da zyklische Beziehungen i.d.R. nicht erwünscht sind und daher beim Einfügen von Teilen zu einem Ganzen verhindert werden sollten. Da es sich um eine Baumstruktur handelt, nennen wir die Basisformen auch Blätter (engl. <i>Leaf</i>). Das folgende Objektdiagramm verdeutlicht eine derartige verschachtelte Baumstruktur, die die obigen Formen weiterverwendet. Die resultierende komplexe Form ist daneben dargestellt. Auf Basis weniger Basisformen können so beliebig komplexe Kompositionen zusammengestellt werden.</p>

<img src="media/patterns_composite_shapes2.png" style="width:1000px">
<label>Zusammengesetzte Form "Vitruvian Man"</label>

<p>Das Entwurfsmuster Kompositum ist nun eine Verallgemeinerung des obigen Beispiels für die 2-dimensionalen Formen, d.h. die zu implementierende Methode heißt allgemein <code>anyOperation</code> anstatt <code>draw</code>, das gemeinsame Interface heißt <code>Component</code> anstatt <code>Shape</code>, usw. Der Klasse Kompositum werden noch Methoden hinzugefügt, um eine Komponente aufzunehmen bzw. zu entfernen. Das folgende UML-Klassendiagramm des Kompositum-Musters entspricht dem darunter stehenden Code.</p>

<img src="media/patterns_composite.png" style="width:500px">
<label>Entwurfsmuster Kompositum</label>

<pre><code class="language-java line-numbers">interface Component {
    void anyOperation();
}

class Leaf implements Component {
    @Override
    void anyOperation() { /* ... */ }
}

class Composite implements Component {
    Set&lt;Component&gt; components = new HashSet<>();
	
    @Override
    void anyOperation() { components.forEach(c -> c.anyOperation()); }
	
	void addComponent(Component c) { components.add(c); }
	void removeComponent(Component c) { components.remove(c); }
}
</code></pre>