Quote:
Originally Posted by NotEnoughForYou
oder man vererbt die Methoden ganz einfach.
|
Angenommen wir haben eine Klasse "PersonViewer", die irgendwie Informationen zu einer Person anzeigt. Die Informationen bekommt die Klasse von einem Objekt von "PersonFinder", denn diese Klasse bietet folgende Methode:
Person findPerson(String name). Das trifft dann, wenn ich die Eingangsfrage richtig verstanden haben, das "Problem" des TEs: Wie greift man in PersonViewer auf PersonFinder zu?
Wie genau darf ich jetzt deinen Vorschlag verstehen, NotEnoughForYou? Möchtest du PersonViewer "ganz einfach" von PersonFinder erben lassen, um Zugriff auf die Methode zu erhalten? Nur, wenn du das wirklich tun möchtest (ich denke eher, dass ich dich falsch verstanden haben): Wieso sollte ein PersonViewer ein PersonFinder sein? Welchen Sinn macht das? Ich denke, dass hier die anderen Lösungen definitiv sinnvoller sind.
Zum Thema: "Singleton" ist ein Design-Pattern (Entwurfsmuster), also eine generische Lösung auf ein häufiges Problem. Das Singleton Pattern wird eingesetzt, wenn es in der ganzen Applikation nur ein Objekt von einer Klasse geben darf und auf dieses eine Objekt von überall zugegriffen werden kann. Mit dem Singleton Pattern könnte sich eine displayPerson(String name) von PersonViewer in etwa so aussehen:
Code:
public void displayPerson(String name) {
Person person = PersonFinder.getInstance().findPerson(name);
// zeige person an
}
Damit hast du eine Lösung - je nach Situation aber keine besonders gute. Das große Problem am Singleton Pattern ist, dass du damit sehr starre Verbindungen zwischen Klasse schaffst.
a) Angenommen, du möchtest deine Applikation mit Unit-Tests testen. Dein PersonFinder findet die Personen in einer Datenbank. Wenn du also einen Test für PersonViewer schreiben möchtest, brauchst du eine entsprechende Datenbank und musst PersonFinder vor der Verwendung von PersonViewer entsprechend einrichten. Eigentlich aber möchtest du nur den PersonViewer testen, und in deiner Testumgebung ist es dir vollkommen egal, woher die Daten eigentlich stammen und ob diese irgendeinen Sinn ergeben. Du kannst die "Quelle" deiner Personen aber nicht austauschen, denn durch das Singleton Pattern ist dein PersonViewer fest an PersonFinder gebunden.
b) Nachdem du deine Anwendung fertig hast, stellt sich plötzlich die Anforderung, die Personen nicht mehr wie bisher aus einer Datenbank sondern von einem entfernten Server zu laden. Da PersonFinder fest in PersonViewer verankert ist, ist es dir nicht möglich, PersonFinder auszutauschen. Du musst also den Quelltext von PersonFinder verändern, um der neuen Anforderung gerecht zu werden. Damit brichst eine Regel des objektorientierten Entwurfs:
Offen für Erweiterungen, geschlossen für Änderungen.
Wie kann man das Problem also besser lösen? Eine gute Lösung gab es schon, nämlich:
Quote:
|
Du könntest aber eine Instanz der Foo Klasse per Konstruktor an die Bar Klasse übergeben...
|
Angenommen, PersonFinder ist in Wirklichkeit nur ein Interface und wir verwenden zunächst DatabasePersonFinder, um Personen zu finden. Wenn wir dem PersonViewer einfach im Konstruktur einen PersonFinder übergeben, haben wir einige Vorteile hingegen der Singleton-Variante, dass wir jederzeit ohne große Änderungen einen anderen PersonFinder verwenden können. In unserer Testumgebung könnten wir dem PersonViewer beispielsweise einen TestPersonFinder übergeben, der immer die gleiche Person zurückgibt. Und wenn sich die Quelle der Personen im produktiven Einsatz ändert, können wir der neuen Anforderung mit einem neuen Finder sehr einfach zurecht werden.
Auch diese Vorgehensweise hat übrigens einen Namen, den du noch oft antreffen wirst, wenn du dich weiter mit OOP beschäftigst: DI (Dependency Injection) und IoC (Inverse of Control).
Dadurch, dass sich PersonViewer nicht mehr selbst um seine Abhängigkeiten kümmert, sondern die Abhängigkeiten von außen
injiziert bekommt (-> DI; in der Praxis geschieht das bei größeren Projekten, wo Objekte oft von ganzen "Objektbäumen" abhängig sind, durch ein Framework, einem sogenannten DI Container), haben wir den normalen Kontrollfluss umgekehrt (-> Inverse of Control). Ein weiteres Beispiel für IoC ist z.B. auch die Ereignisorientierte Programmierung (Stichwort GUI-Systeme). In "klassischen" Programmen wird die Anwendung nach dem Start initialisiert und verfällt dann in eine Endlosschleife, in der geprüft wird, ob z.B. irgendwo hin geklickt wurde oder eine Taste gedrückt wurde. Bei "modernen" GUI-Systemen läuft das anders ab: Die Anwendung wird gestartet und initialisiert. Anschließend gibt das Programm die Kontrolle an das Framework / GUI-System ab. Über Ereignisse (Events) wird das Programm dann benachrichtigt, wenn es eine Interaktion o.ä. mit dem Programm gibt. Also wurde auch hier der normale Kontrollfluss umgedreht - nicht mehr das Programm ruft das Framework auf sondern das Framework das Programm -> IoC. Aber gut, das hat wohl nicht mehr viel mit der Frage zu tun ...