Fabrik Entwurfsmuster. Welche Implementierung.
-
Hallo zusammen,
Ich habe eine Gruppe von Klassen die alle von einer abstrakten Basisklasse erben. Sie sollen ausschließlich als Basisklassenzeiger verwendet werden. Es ist also Naheliegend nur die Konstruktion durch Fabrikfunktionen zu erlauben. Hier kommen wir jedoch zwei Arten in den Sinn, dies zu tun.1. Ein Modul. Der Konstruktor der Klasse ist privat. Die Fabrikfunktion ist entweder eine statische Funktion der Klasse oder eine befreundete Funktion der Klasse.
2. Zwei Module. Eins für die Klasse und eines für die Fabrikfunktion. Nur das Modul der Fabrikfunktion ist teil der Benutzerschnittstelle. Die Klasse ist also nicht sichtbar und somit auch nicht direkt instanzierbar.
Welchen weg würdet ihr einschlagen? Ich bin ein wenig unschlüssig. Die zweite Lösung scheint mit die sauberere zu sein. Allerdings ist es ein wenig nervig für jede Klasse zwei Module zu haben. Eventuell kommen später noch andere Klassen hinzu, die Zugriff auf den Konstruktor brauchen. Dann müsste ich diese natürlich auch als befreundet angeben.
Oder gibt es vielleicht eine dritte Lösung, die ich hier noch nicht berücksichtigt habe?
Danke,
Martin Kalbfuß
-
Wie wärs mit Fabrikfunktionen, die Basisklassenzeiger zurückgeben? Die konkrete Klasse bleibt für den Benutzer komplett verborgen, du kannst sie sogar in einer .cpp-Datei definieren. Sichtbar ist genau eine Fabrikfunktion im Header. Damit hast du auch minimale Abhängigkeiten.
// XY.hpp Base* CreateXY(); // XY.cpp #include "Base.hpp" class XY : public Base { ... }; Base* CreateXY() { ... }friendist also nicht weitreichend. Du kannst es sogar ganz weglassen, da dieXY-Klasse so lokal ist, dass Kapselung keinen wesentlichen Vorteil bringt.
-
Martin Kalbfuß schrieb:
Sie sollen ausschließlich als Basisklassenzeiger verwendet werden. Es ist also Naheliegend nur die Konstruktion durch Fabrikfunktionen zu erlauben.
Warum. Ich seh da nich ganz die Verbindung!?
-
dot schrieb:
Warum. Ich seh da nich ganz die Verbindung!?
Die Alternative zu Fabrikfunktionen ist der Konstruktor. Dazu muss die konkrete abgeleitete Klasse bekannt sein, was aber nicht viel bringt, wenn sie sowieso nur über die abstrakte Basisklasse angesprochen wird.
-
Nexus schrieb:
dot schrieb:
Warum. Ich seh da nich ganz die Verbindung!?
Die Alternative zu Fabrikfunktionen ist der Konstruktor. Dazu muss die konkrete abgeleitete Klasse bekannt sein, was aber nicht viel bringt, wenn sie sowieso nur über die abstrakte Basisklasse angesprochen wird.
Wieso? Was hat die Tatsache dass andere Objekte von dem Objekt nur das Basisinterface sehen damit zu tun, wie das Objekt instanziert wird? Wenn du mich fragst sind das erstmal zwei völlig orthogonale Dinge. Die Fragen die es zu beantworten gilt sind: Was ist der Scope des Objektes? Ist an der Stelle wo das Objekt erzeugt wird der konkrete Typ bekannt oder nicht? Wenn nein, warum nicht? Die Tatsache dass nun "ausschließlich Basisklassenzeiger" verwendet werden ist vielleicht mit den Antworten auf diese Fragen verknüpft, ist an sich aber keine Antwort und daher kann man rein auf Basis dieser Aussage auch nicht entscheiden, ob man nun eine Factory will oder nicht, es fehlt einfach die wesentliche Information. Zumindest meiner Meinung nach

-
Zur ersten Antwort. Das würde natürlich auch gehen. Dann könnte ich zwei headers haben einen für die Benutzerschnittstelle mit Fabrik und einen für interne Verwendungszwecke mit zugriff auf den Konstruktor. Drei statt vier Dateien und trotzdem eine saubere Implementierung ohne die Verwendung von friend.
Das scheint mir die beste Lösung zu sein. Danke.Zur zweiten. So ganz sicher bin ich mir da nicht. Kurz um. Der einzig entscheidende Bestandteil einer jeden Klasse ist eine überschriebene virtuelle Methode. Es ist also ausreichend die Klassen als Basisklassenzeiger herum zu reichen. Das ist natürlich noch keine Rechtfertigung. Es geht hier um automatische Speicherverwaltung. Aber vielleicht hab ich mich ja auch falsch entschieden. Wann sollte man deiner Meinung nach dieses Entwurfsmuster verwenden?
Vielleicht verschafft mir das einen neuen Blick auf meinen Code. So ganz glücklich bin ich damit auch nicht, da shared_ptr denke ich einen höheren Speicherverbrauch an den Tag legt als ein einfacher word_t*.
-
Martin Kalbfuß schrieb:
Ich habe eine Gruppe von Klassen die alle von einer abstrakten Basisklasse erben. Sie sollen ausschließlich als Basisklassenzeiger verwendet werden. Es ist also Naheliegend nur die Konstruktion durch Fabrikfunktionen zu erlauben.
Verstehe ich auch nicht.
Warum eine Fabrik machen?
-
Beantworte dir doch einfach mal folgende Fragen:
Was ist der Scope des Objektes bzw. seine Lebensdauer?
Wo wird es instanziert?
Wo wird es freigegeben?
Wer instanziert das Objekt?
Wer gibt es frei?
Warum?Noch ist weder von shared_ptr noch überhaupt von new oder delete die Rede. Es könnte genau so gut einfach eine lokale Variable in irgendeiner Funktion sein, oder ein Member eines anderen Objektes. Deine "Basisklassenzeiger" bekommst du immer, egal wie das Objekt instanziert wird.
Der Punkt ist: Aufgrund der momentanen Informationslage, kann man einfach nicht entscheiden was die beste Lösung wäre, dazu müsste man dein konkretes Problem besser kennen...
-
Was ist der Scope des Objektes? Ist an der Stelle wo das Objekt erzeugt wird der konkrete Typ bekannt oder nicht?
Ich bin mir nicht sicher ob ich das verstanden habe.
Also ob der Benutzer meiner Bibliothek weiß, welchen Objekttyp er da gerade konstruiert?! Jeder Typ hat seinen eigene Fabrikfunktion, die nach dem zu konstruierenden Typ benannt ist. Somit würde ich das mit ja beantworten. Aber ich vermute mal dass ich's noch nicht so ganz habe...Grund waren Literale. Ich wollte diese direkt in der Parameterliste konstruieren können, ohne sie jedes mal benennen zu müssen.
auto inc = vm::compound(vm::constant(1), vm::add());Und nicht
auto the_number_one = vm::constant(1); auto inc = vm::compound(the_number_one, vm::add());Intern werden nur Zeiger verwendet. Es wird also keine Kopie erstellt.
-
OK. Danke Leute. Ich habe mir die Fragen zu Herzen genommen, und festgestellt, dass die Objekte immer dort freigegeben werden, wo sie auch instanziert werden. Ich habe das jetzt so interpretiert, dass es nicht nötig ist die Objekte durch eine Fabrik zu konstruieren. Bleibt das Literalproblem.
Ich glaube aber eine Lösung gefunden zu haben. Danke liebe Templates.
template <int value> word_t &literal() { static constant_t literal(value); return literal; }Das müsste, wenn ich das richtig sehe für jede Zahl ein Singleton erstellen. Ohne den Heap anzufassen und macht damit automatische Speicherbereinigung überflüssig.