eingebetteten Objekte sind ja von ihrer Mutter nicht unabhängig, sprich es sind nicht wirklich - wie Du es siehst - unabhängige Module.
Yup. Guter Punkt und ich stimme 100% überein (ausser die Benutzung von Mutter in diesem Kontext, aber da mag ich ein wenig fanatisiert sein).
Die UML spezifiziert 2 besondere Arten von Ganzes-Teile Beziehungen, d.h. Container (hier Maske) has [1, lots of] in Maske enthaltene Objekte eines bestimmten Typs (hier: Buttons).
Ganzes-Teile Beziehungen sind wiederum eine besonder Untergruppe von Assoziationen. Assoziationen haben
nichts, null, nil, nada, zilch mit Vererbung zu tun. Wg. dieser fundamentalen Unterscheidung ist der Begriff Container-Objekt für Ganzes-Teile Beziehung allgemein gebräuchlicher als Parent/Mutter/Vater etc., da mit letzeren allgemein Vererbungsphänomene bezeichnet werden, aber das nur am Rande.*
1. Aggregation: 1 Auto hat 4 Räder. Das Auto ist ohne die Räder nicht komplett. Die Räder existieren aber für sich selber, dh. können potentiell länger und unabhängig vom Auto leben. Wie noch zu sehen sein wird, gibt es auch Fälle, wo die abhängigen Objekte von mehreren Container-Objekten gleichzeitig verwendet werden können.
2. Komposition (stärkere Beziehung): Das assoziierte Objekt kann über seinen gesamten Lebenszyklus nur zu einem Ganzen gehören und seine Existenz ist von der Existenz des Container-Objektes, zu dem er assoziiert ist, abhängig.
Beispiel: Das Blatt von baumA kann nicht mehr von baumB benutzt werden, nachdem baumA abgestorben ist oder baumA die Aktivität werfeBlattAbEsWirdWinter() durchgeführt hat. baumB muss vielmehr das Blatt ganz neu erzeugen.
Wenn code hinter einem Button nicht in einer Skript-Bibliothek enthalten ist, sondern direkt in den Button hineinprogrammiert wird, dann wird dieser code in der Maske geboren und ist in seinem Lebenszyklus 100% an die Maske gebunden, dh. er verschwindet, wenn die Maske gelöscht wird (Kompositions-Beziehung)
Es würde Sinn machen, wenn der Button bei einer Kompositionsbeziehung das Option Declare des Container-Objekts (=Maske) übernähme.
{ und übernähme steht hier
FREAKIN' NOT!!! für eine Vererbung.
A Button IS
NOT A Form.
Vielmehr A Form
HAS [0...n] Button
Die Button stehen zwar mit der Maske in Beziehung, es ist aber keine Vererbungsbeziehung.
Übernhemen heisst, dass das Property optionDeclare in den Button in Abhängigkeit von dem Property optionDeclare der Form gesetzt wird.
In OO wird das so gemacht (und kommt mir nicht mit Mehrfachvererbung. Das gilt mittlerweile als über-komplexes feature:
class Form {
private optionDeclare;
private List buttons = new ArrayList();
public setOptionDeclare (boolean val) {
// abhängige Objekte manipulieren:
Iterator it = buttons.iterator();
while (it.hasNext()) {
(Button) it.next()).setOptionDeclare(val);
}
optionDeclare = val;
}
[..]
}
Button {
[...]
private boolean optionDeclare;
public setOptionDeclare (boolean val) { optionDeclare = val;}
[..]
}
}
Sobald der Code in eine Skriptlib ausgelagert wird, sieht es natürlich ganz anders aus.
Dann haben wir nämlich eine Aggregationsbeziehung. Potentiell oder real verwenden mehrere Komponenten den Code der gleichen Skriptlib. Dieses Code-Objekt ist nicht mehr einem eindeutigen Container-Objekt zugeordnet.
Ähnliches Beispiel aus CAD-mässiger Anwendung in Fowler, UML-Destilled:
Ein Polygon hat mehrere Punkt Objekte und mit der Umrandung ist ein Stil-Objket verbunden (gepunktete Linie, durchgezogene Linie, etc.).
Die Punkte haben eine Komposition-Beziehung zum Polygon (wie Button code nicht-in-ScriptLib).
Wenn das Polygon gelöscht wird, werden auch die Punkt-Objekte gelöscht.
Der Stil der Umrandung hat eine Aggregations-Beziehung zum Polygon. Ein Stil Objekt wird ja in aller Regel so gestaltet, dass er von mehreren Polygon-Objekten verwendet werden kann (wie code in ScriptLib). D.h. wenn das Polygon gelöscht wird, wird das Stil-Objekt nicht gelöscht.
// beginner - intermediate level code, d.h. newbies verwirrt das :-)
class Polygon {
private ArrayList points(x, y); // die Menge an individuellen Punkten wird gelöscht, wenn Polygon gelöscht wird
private Style style; /* Hier: Kopien von dem gleichen Style Objekt werden von mehreren Objekten benutzt. Nicht an Lebenszyklus Polygon gebunden */
public void addPoint(int pos, int x, int y) {
// das Polygon ist selbst Creator dieses Objekts. Hinweis auf Composition Beziehung
Point point = new Point (x, y);
points.add(pos, point);
}
public void setStyle(String nameOfStyle) {
/* das Polygon holt sich Objekt von externen Factory, wo diese unabhängig erzeugt und möglicherweise gecached, von mehreren Objekten gleichzeitig verwendet wird. Hinweis auf Aggregation */
style = StyleFactory.getInstance().create(nameOfStyle);
}
}
class Point {
private int x;
private int y;
public Point (int x, int y) {
this.x = x;
this.y = y;
}
public void getX() {
return x;
}
public void getY() {
return y;
}
}
class StyleFactory {
/* implementiert als SINGLETON (es gibt in der Anwendung immer genau 1 StyleFactory-Objekt, */
private static StyleFactory instance = new StyleFactory();
private HashMap cache;
private Style defaultStyle; // speziell definierter Style, der benutzt wird, wenn Factory nicht weiterweiss.
// implementiere Singleton Pattern
public static StyleFactory getInstance() {
return instance;
}
// constructor
private StyleFactory() {
cache = new HashMap();
defaultStyle = readSomeDatabaseToLookWhatsDefaultStyle
}
public Style create (String nameOfStyle) throws StyleOfCacheNotFoundException {
// lookup cache für entsprechende Kopie
Style style = cache.get(nameOfStyle);
if (style==null) {
try {
// NEU. Waren größere Fehler
style = lookInDatabaseForDefinitionForThisTypeOfNameOfStyleAndCreateIt(nameOfStyle);
cache.put(nameOfStyle, style); // style in cache tun
} catch (StyleOfCacheNotFoundException e) {
// Programmierer soll sich was gutes einfallen lassen.
// z.B.:
tellUserThatStyleWithThisNameCannotBeFound();
style = defaultStyle;
e.printStackTrace();
}
return style;
}
Bei diesem Selbstversuch, wie ich das implementieren würde, ist mir etwas interessantes aufgefallen:
Von einer reinen Code-Perspektive her würden alle Polygone ein eigene Kopie von Style Objekt verwenden.
Aus einer Design-Perspektive heraus ist es jedoch Aggregation. Die eigentlich kostspielige Operation bei der Erzeugung der Style-Objekte ist nämlich:
lookInDatabaseForDefinitionForThisTypeOfNameOfStyleAndCreateIt();
Wenn ein Objekt erstmal in der cache-HashMap liegt, ist es nämlich wesentlich ressourcenschonender, sich eine Kopie zu besorgen als wenn diese neu erzeugt werden müsste.
Somit kann man davon sprechen, dass die Style Objekte von verschiedenen Polygonen wiederverwendet werden.
Es ist aber - wie vieles in OO - ein bischen unscharf, nicht-trivial und komplex.
UML Symbole:
Aggregation: nicht ausgefüllte Raute.
Komposition: ausgefüllte Raute.
Die Unterscheidung zwischen Aggregation und Komposition wird oft als nicht so praxisrelevant dargestellt. In diesem Fall (Maske - code hinter Button - code referenziert aus ScriptLib) macht er aber Sinn.
Gruß Axel
---------------------------------
* es gibt 3 Arten von grundlegenden Beziehungen zwischen Objekten/Interfaces
is a: Vererbung. In Java realisiert durch extends
is like a: Interface Implementierung. In Java realisiert durch implements
has a: Assoziation. In Java realisiert durch Referenz auf anderes Objekt
Vlissides, Johnson, etc., Design Patterns von 1995 bedeutete wohl einen Paradigmenwechsel für OO, der u.a. auch darin besteht, dass es die Bedeutung von "is like a" und "has a" Beziehungen gegenüber Vererbung betonte. Jeder gute Java/C++/C#/Smalltalk Programmierer hat das im Kopf. Aber das nur am Rande.
Viele newbies (mein jetzt nicht dich, Jens) haben, wenn sie an OO denken zu sehr Vererbung im Kopf. Vererbung ist aber nicht der wichtigste Punkt.