Stellen Sie sich vor, Sie sind beauftragt, eine Applikation zu entwickeln, die zur Unterstützung der Personalwirtschaft einer Firma eingesetzt werden soll. Unsere - doch recht simpel gestrickte - Anwendung kennt verschiedene Positionen, die ein Mitarbeiter wahrnehmen kann; er kann als Manager, Ingenieur und Verkäufer im Unternehmen tätig sein. Abhängig von seiner Rolle sind verschiedene Eigenschaften des Mitarbeiters zu unterschiedlichen Zeiten interessant. Einem Manager sind beispielsweise verschiedene Projekte zugeordnet, für die er verantwortlich ist, während bei einem Ingenieur möglicherweise die Zahl seiner angemeldeten Patente von Interesse ist. Bei einem Verkäufer wiederum sind die von ihm erreichten Verkaufszahlen ausschlaggebend, z.B. um ihm eine angemessene Prämie zukommen zu lassen. Denkbar ist auch, dass alle Rollen im Unternehmen eine gemeinsame Eigenschaft besitzen, ihr konkreter Wert jedoch von einer Rolle abhängig ist. Denken Sie z.B. an ein Budget, das einem Mitarbeiter zugeordnet ist. So kann ein Manager bzw. Abteilungsleiter sicherlich über größere Geldbeträge verfügen als ein Ingenieur oder Verkäufer. Abpictureung 1 zeigt die eben geschilderte Situation.
- Ein Mitarbeiter kann zu einem Zeitpunkt verschiedene Rollen im Unternehmen spielen.
- Die Rollen eines Mitarbeiters können über einen Zeitraum wechseln.
Die Trivialmethode
Die einfachste Vorgehensweise wird es wohl sein, alle kontextspezifischen Anforderungen in einer Schnittstelle zu verpacken - Einen entsprechenden Vorschlag sehen Sie in Abpictureung 2. Derartig entworfene Schnittstellen sind schwer zu verstehen und noch schwerer zu pflegen und zu unterhalten. Unvorhergesehene Änderungen werden höchstwahrscheinlich Änderungen in großen Teilen der Applikation nach sich ziehen.
Vererbung
Ein weiterer Ansatz bestünde darin, den Typ Person mittels Vererbung zu erweitern und neue Typen Manager, Ingenieur und Verkäufer hinzuzufügen, die kontextspezifisches Verhalten definieren. Dies ist in Abpictureung 3 dargestellt. Nun stellt sich jedoch ein neues Problem ein: Eine Person, die sowohl als Manager als auch Ingenieur im Unternehmen auftritt, wird dann in der Anwendung von zwei unterschiedlichen Objekten repräsentiert, obwohl es sich eindeutig um ein und dieselbe Person handelt. Identität könnte dann nur durch zusätzliche Mittel simuliert werden - z.B. müsste ein Exemplar der Manager-Klasse alle anderen Exemplare der Klassen Ingenieur und Verkäufer über eine Änderung des Namens oder Vornamens informieren, um Datenintegrität zwischen den verschiedenen Objekten sicherzustellen. Dass die Implementierung eines solchen Mechanismus' alles andere als trivial ausfallen wird, muss kaum hinzugefügt werden. Wiederum ist zu sagen, dass auch dieser Ansatz durchaus seine Berechtigung hat. Sie können ihn verwenden, wenn Sie sicher sind, dass zu einem Zeitpunkt nur ein Exemplar einer Rolle verwendet wird - genau dann müssen Sie sich keine Gedanken machen, ob und wie Zustandsänderungen die Datenintegrität beeinflussen.
State Pattern
Das State Pattern ermöglicht es einem Objekt, sein Verhalten während der Laufzeit zu ändern, indem sich sein interner Zustand dem jeweiligen Kontext anpasst. Ein Client dieses Objekts wird den Eindruck haben, dass das Objekt seine Implementierung (aber nicht seinen Typ!) gewechselt habe. Das Klassendiagramm des Zustandsmusters, angewandt auf unser Problem, ist in Abpictureung 4 zu sehen - eine ausführliche Diskussion des State Pattern findet sich in [1]
Role Object
Kern dieses Artikels pictureet das - im Folgenden beschriebene - Role Object Pattern. Eine ausführliche Beschreibung verschiedener Varianten dieses Musters findet sich in [2].
Static Roles
Diese Variante des Role Object Patterns verlangt, dass Sie sich bereits zur Compile-Zeit der Menge aller Rollen, die eine Entität spielen kann, bewusst sind. Das heißt, dass eine Person nur in den genannten Rollen des Managers, Ingenieurs oder Verkäufers auftreten kann; andere Rollen wie z.B. die Rolle eines Conultants können von einem Personen-Objekt nicht übernommen werden. Dies mag zwar im ersten Augenblick als ein gravierender Nachteil erscheinen, ist aber in vielen Fällen ausreichend. Der große Vorteil dieses Musters liegt darin begründet, dass bereits der Compiler prüfen kann, dass Sie Objekte nur in diejenigen Rollen versetzen, die das Objekt auch wirklich spielen kann. Um dieses Feature zu verwenden, werde ich Ihnen im Abschnitt Implementierung zeigen, wie Sie den Mechanismus der Operatorüberladung nutzen können.Dynamic Roles
Die dynamische Vergabe von Rollen stellt zweifelsohne die mächtigste Variante aller besprochenen Muster dar. Sie erlaubt es Ihnen, Objekten zur Laufzeit verschiedene Rollen hinzuzufügen und diese auch wieder zu entfernen. Dazu müssen im Personen-Objekt Operationen definiert werden, die die Verwaltung von Rollen ermöglicht (Abb. 6). Dieser Ansatz birgt natürlich höhere Komplexität in sich und versteckt noch weitere Tücken: Sie müssen sicher gehen, dass Objekten keine Rollen zugewiesen werden, die sie nicht ausführen können - beim dynamischen Ansatz ist es nicht möglich, dass Sie der Compiler vor derartigem Missbrauch bewahrt.
Implementierung
Bisher haben wir ja sehr viel Zeit mit der Diskussion der konzeptionellen Grundlagen verbracht - nun wollen wir der Realisierung unserer Ideen nachgehen. Ich möchte mich hier speziell, wie angekündigt, dem Static Roles-Muster widmen. C# und das .NET Framework geben uns einen für diesen Zweck sehr eleganten Mechanismus an die Hand: Operatorüberladung, speziell die Überladung des cast-Operators. Wenn Sie sich näher mit den Möglichkeiten der Operatorüberladung des .NET Frameworks beschäftigen möchten, kann ich Ihnen den Artikel Als die Klassen rechnen lernten [3] empfehlen. Eine exemplarische Implementierung der Klassen Person, Manager, Ingenieur und Verkäufer finden Sie in Listings 1 bis 4. Lenken Sie Ihre Aufmerksamkeit bitte auf folgende Punkte: Das Dictionary Roles der Klasse Person hält eine Referenz auf alle Rollen-Exemplare, die ein Objekt vom Typ Person spielen kann. Die Überladung des cast-Operators erlaubt es uns, sehr elegant auf die einzelnen Aspekte eines Angestellten zuzugreifen. In anderen Sprachen, wie z.B. Java wären dagegen weitere Methoden erforderlich, die uns die gewünschte Funktionalität zur Verfügung stellen. Die Handhabung des cast-Operators soll Ihnen der Code-Auszug in Listing 5 vermitteln, der das Personalstammblatt aus Abpictureung 1 erzeugt. Listing 1public class Person{Dictionary Roles = new Dictionary();//// Überladung des cast-Operators//public static explicit operator Manager(Person person){return (Manager)person.Roles["Manager"];}public static explicit operator Ingenieur (Person person){return (Ingenieur)person.Roles["Ingenieur"];}public static explicit operator Verkäufer (Person person){return (Verkäufer)person.Roles["Verkäufer"];}public static Person GetPersonByName(String Name, String Vorname){// Suchfunktion ..}public Person(){Roles.add("Manager", new Manager());Roles.add("Ingenieur", new Ingenieur());Roles.add("Verkäufer", new Verkäufer());}public String Name{// ..}public String Vorname{// ..}}
public class Manager{public Manager(){// ..}public int Projekte{// ..}}
public class Ingenieur{public Ingenieur(){// ..}public int Patente{// ..}}
public class Verkäufer{public Verkäufer(){// ..}public int Verkaufszahlen{// ..}}
// Mitarbeiter Dominik Tornow im Datenbestand suchenPerson person = Person.GetPersonByName ("Dominik", "Tornow");..// Allgemeine InformationenConsole.Out.WriteLine ("Name: " + person.Name);Console.Out.WriteLine ("Vorname: " + person.Vorname);<u>try</u><a>[Author ID0: at ]</a>{// Informationen, die einen Manager betreffenManager manager = (Manager) person;Console.Out.WriteLine ("Budget: " + manager.Budget);Console.Out.WriteLine ("Projekte: " + manager.Projekte);// Informationen, die einen Ingenieur betreffenIngenieur ingenieur = (Ingenieur) person;Console.Out.WriteLine ("Budget: " + ingenieur.Budget);Console.Out.WriteLine ("Patente: " + ingenieur.Patente);// ..}catch(NotInRoleException r){// ..}catch(Exception e){// ..}
Fazit
Die hier vorgestellten Möglichkeiten geben Ihnen ein Werkzeug an die Hand, mit kontextspezifischen Anforderungen an First-Class Entitäten - auch über Anwendungsgrenzen hinweg - umzugehen. Dies ist jedoch noch keine erschöpfende Diskussion der verwendbaren Mittel. Gerade das hier diskutierte Rollenspiel lässt sich in einer Unzahl von Variationen wiederfinden. Ich hoffe, ich konnte Ihnen eine verständliche Einführung in das Thema bieten und Ihnen einen ersten Eindruck der unterschiedlichen Möglichkeiten verschaffen. Der interessierte Leser sei an dieser Stelle auf den - wie ich persönlich finde - ausgezeichneten Artikel Dealing with Roles von Martin Fowler [2] verwiesen.Links und Literatur
- [1] E. Gamma, R. Helm, R. Johnson, J.Vlissides, Entwurfsmuster, Addison-Wesley
- [2] Martin Fowler, Dealing with Roles: www.martinfowler.com/apsupp/roles.pdf
- [3] Patrick A. Lorenz, Als die Klassen rechnen lernten, dot.net magazin, 03.2002; online unter www.dotnet-magazin.de/
- [4] Dirk Bäumer, Dirk Riehle, Wolf Siberski, Martina Wulf, The Role Object Pattern: jerry.cs.uiuc.edu/~plop/plop97/Proceedings/riehle.pdf




