Konstruktoren gegen Fabrikmethoden [geschlossen]


180

Welche Art der Initialisierung wird beim Modellieren von Klassen bevorzugt:

  1. Konstruktoren oder
  2. Fabrikmethoden

Und was wären die Überlegungen für die Verwendung von beiden?

In bestimmten Situationen bevorzuge ich eine Factory-Methode, die null zurückgibt, wenn das Objekt nicht erstellt werden kann. Dies macht den Code ordentlich. Ich kann einfach überprüfen, ob der zurückgegebene Wert nicht null ist, bevor ich eine alternative Aktion ausführe, im Gegensatz zum Auslösen einer Ausnahme vom Konstruktor. (Ich persönlich mag keine Ausnahmen)

Angenommen, ich habe einen Konstruktor für eine Klasse, der einen ID-Wert erwartet. Der Konstruktor verwendet diesen Wert, um die Klasse aus der Datenbank zu füllen. Wenn kein Datensatz mit der angegebenen ID vorhanden ist, löst der Konstruktor eine RecordNotFoundException aus. In diesem Fall muss ich die Konstruktion all dieser Klassen in einen try..catch-Block einschließen.

Im Gegensatz dazu kann ich für diese Klassen eine statische Factory-Methode verwenden, die null zurückgibt, wenn der Datensatz nicht gefunden wird.

Welcher Ansatz ist in diesem Fall besser, Konstruktor oder Fabrikmethode?

Antworten:


66

Ab Seite 108 von Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software von Gamma, Helm, Johnson und Vlissides.

Verwenden Sie das Factory-Methodenmuster, wenn

  • Eine Klasse kann die Klasse von Objekten, die sie erstellen muss, nicht vorhersehen
  • Eine Klasse möchte, dass ihre Unterklassen die von ihr erstellten Objekte angeben
  • Klassen delegieren die Verantwortung an eine von mehreren Hilfsunterklassen, und Sie möchten das Wissen lokalisieren, welche Hilfsunterklasse der Delegierte ist

21
Die statische Factory-Methode unterscheidet sich vom GoF-Entwurfsmuster - Factory Method Pattern. stackoverflow.com/questions/929021/…
Sree Rama

Bitte vergleichen Sie nicht mit dem Factory-Methodenmuster von GoF-Entwurfsmustern.
Sree Rama

136
das erklärt mir nichts
Sushant

@Sushant, warum ist es so?
PaulD

2
Diese Antwort beantwortet nicht die Frage, sondern gibt nur Informationen zum Lesen weiter, um das Konzept zu verstehen / zu erklären ... Dies ist eher ein Kommentar.
Crt

201

Fragen Sie sich, was sie sind und warum wir sie haben. Beide dienen dazu, eine Instanz eines Objekts zu erstellen.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Bisher kein Unterschied. Stellen Sie sich nun vor, wir haben verschiedene Schultypen und möchten von ElementarySchool zu HighSchool wechseln (das von einer ElementarySchool abgeleitet ist oder dieselbe Schnittstellen-ISchool wie die ElementarySchool implementiert). Die Codeänderung wäre:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

Im Falle einer Schnittstelle hätten wir:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Wenn Sie diesen Code nun an mehreren Stellen haben, können Sie sehen, dass die Verwendung der Factory-Methode ziemlich billig sein kann, da Sie nach dem Ändern der Factory-Methode fertig sind (wenn wir das zweite Beispiel mit Schnittstellen verwenden).

Und das ist der Hauptunterschied und Vorteil. Wenn Sie sich mit komplexen Klassenhierarchien befassen und dynamisch eine Instanz einer Klasse aus einer solchen Hierarchie erstellen möchten, erhalten Sie den folgenden Code. Factory-Methoden verwenden dann möglicherweise einen Parameter, der der Methode mitteilt, welche konkrete Instanz instanziiert werden soll. Angenommen, Sie haben eine MyStudent-Klasse und müssen das entsprechende ISchool-Objekt instanziieren, damit Ihr Schüler Mitglied dieser Schule ist.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Jetzt haben Sie eine Stelle in Ihrer App, die Geschäftslogik enthält, die bestimmt, welches ISchool-Objekt für verschiedene IStudent-Objekte instanziiert werden soll.

Also - für einfache Klassen (Wertobjekte usw.) ist der Konstruktor in Ordnung (Sie möchten Ihre Anwendung nicht überentwickeln), aber für komplexe Klassenhierarchien ist die Factory-Methode eine bevorzugte Methode.

Auf diese Weise folgen Sie dem ersten Entwurfsprinzip der vierköpfigen Gruppe "Programm zu einer Schnittstelle, nicht zu einer Implementierung".


2
Selbst wenn Sie denken, dass es sich um eine einfache Klasse handelt, besteht die Möglichkeit, dass jemand Ihre einfache Klasse erweitern muss, sodass die Factory-Methode immer noch besser ist. Sie können beispielsweise mit ElementarySchool beginnen, aber später kann es jemand (einschließlich Sie selbst) mit PrivateElementarySchool und PublicElementarySchool erweitern.
Jack

10
Dies sollte die akzeptierte Antwort sein
am05mhz

2
@ David, gute Antwort, aber können Sie ein Beispiel erweitern, bei dem für jede Schnittstellenimplementierung möglicherweise andere Parameter für die Erstellung erforderlich sind. Hier ist ein dummes Beispiel: IFood sandwich = new Sandwich(Cheese chz, Meat meat);Und IFood soup = new Soup(Broth broth, Vegetable veg);wie können Fabrik und / oder Baumeister hier helfen?
Brian

1
Ich habe gerade drei weitere Erklärungen zum Zweck der Verwendung in der Fabrik gelesen, und dies ist diejenige, bei der ich endlich "klicken" musste. Danke dir!
Daniel Peirano

Warum wird diese Antwort nicht akzeptiert?
Tomas

74

Sie müssen lesen (wenn Sie Zugriff darauf haben). Effektives Java 2 Punkt 1: Betrachten Sie statische Factory-Methoden anstelle von Konstruktoren .

Vorteile der statischen Fabrikmethoden:

  1. Sie haben Namen.
  2. Sie müssen nicht bei jedem Aufruf ein neues Objekt erstellen.
  3. Sie können ein Objekt eines beliebigen Subtyps ihres Rückgabetyps zurückgeben.
  4. Sie reduzieren die Ausführlichkeit beim Erstellen parametrisierter Typinstanzen.

Nachteile der statischen Fabrikmethoden:

  1. Wenn nur statische Factory-Methoden bereitgestellt werden, können Klassen ohne öffentliche oder geschützte Konstruktoren nicht in Unterklassen unterteilt werden.
  2. Sie sind nicht leicht von anderen statischen Methoden zu unterscheiden

4
Dies scheint mir eher ein schwerwiegender Fehler in Java zu sein, dann ein allgemeines OOD-Problem. Es gibt zahlreiche OO - Sprachen , die nicht einmal zu tun haben Konstrukteure, noch Subklassen funktioniert gut.
Jörg W Mittag

1
@cherouvim, warum meistens Code mit Konstruktoren geschrieben wird, wenn( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

Gute Argumente. Es ist jedoch Java-spezifisch. Es kann ein Fall für eine Sprachfunktion angeführt werden, die Factory-Methoden von anderen statischen Methoden unterscheidet.
OCDev

30

Standardmäßig sollten Konstruktoren bevorzugt werden, da sie einfacher zu verstehen und zu schreiben sind. Wenn Sie jedoch speziell die Konstruktionsmerkmale eines Objekts von seiner semantischen Bedeutung im Sinne des Client-Codes entkoppeln müssen, sollten Sie Fabriken verwenden.

Der Unterschied zwischen Konstruktoren und Fabriken ist beispielsweise analog zu einer Variablen und einem Zeiger auf eine Variable. Es gibt eine andere Ebene der Indirektion, was ein Nachteil ist. Es gibt aber auch ein anderes Maß an Flexibilität, was von Vorteil ist. Wenn Sie also eine Wahl treffen, sollten Sie diese Kosten-Nutzen-Analyse durchführen.


17
Also, (TDD-Stil) würden Sie mit Konstruktoren beginnen, um die Arbeit am einfachsten zu erledigen. Und dann zu Fabriken umgestalten, sobald Sie Code-Gerüche bekommen (wie wiederholte bedingte Logik, die bestimmt, welcher Konstruktor aufgerufen werden soll)?
AndyM

1
Sehr wichtiger Punkt. Eine Benutzerstudie, in der Fabriken und Konstruktoren verglichen wurden, ergab hoch signifikante Ergebnisse, die darauf hinweisen, dass Fabriken die API-Benutzerfreundlichkeit beeinträchtigen: "Benutzer benötigen erheblich mehr Zeit (p = 0,005), um ein Objekt mit einer Factory als mit einem Konstruktor zu erstellen " [Das Factory-Muster im API-Design : Eine Usability-Bewertung ].
Mdeff

12

Verwenden Sie eine Factory nur, wenn Sie zusätzliche Kontrolle bei der Objekterstellung benötigen, wie dies mit Konstruktoren nicht möglich ist.

Fabriken haben beispielsweise die Möglichkeit, zwischenzuspeichern.

Eine andere Möglichkeit, Fabriken zu verwenden, besteht in einem Szenario, in dem Sie den Typ, den Sie erstellen möchten, nicht kennen. Oft sehen Sie diese Art der Verwendung in Plugin-Factory-Szenarien, in denen jedes Plugin von einer Basisklasse abgeleitet sein oder eine Art Schnittstelle implementieren muss. Die Factory erstellt Instanzen von Klassen, die von der Basisklasse abgeleitet sind oder die Schnittstelle implementieren.


11

Ein Zitat aus "Effective Java", 2. Aufl., Punkt 1: Betrachten Sie statische Factory-Methoden anstelle von Konstruktoren. 5:

"Beachten Sie, dass eine statische Factory-Methode nicht mit dem Factory-Methodenmuster aus Design Patterns [Gamma95, S. 107] identisch ist. Die in diesem Artikel beschriebene statische Factory-Methode hat keine direkte Entsprechung in Design Patterns."


10

Neben "effektivem Java" (wie in einer anderen Antwort erwähnt) schlägt ein anderes klassisches Buch auch vor:

Bevorzugen Sie statische Factory-Methoden (mit Namen, die die Argumente beschreiben) gegenüber überladenen Konstruktoren.

Z.B. schreibe nicht

Complex complex = new Complex(23.0);

sondern schreiben

Complex complex = Complex.fromRealNumber(23.0);

Das Buch geht so weit, den Complex(float)Konstruktor privat zu machen, um den Benutzer zu zwingen, die statische Factory-Methode aufzurufen.


2
Das Lesen dieses Teils des Buches brachte mich hierher
Purple Haze

1
@ Bayrem: Ich auch, ich habe es kürzlich noch einmal gelesen und dachte, ich sollte es zu den Antworten hinzufügen.
blue_note

1
Über einen entsprechenden Hinweis, finden Sie vielleicht hilfreich einige Namenskonventionen durch die arbeitet java.time Rahmen in Bezug auf die Benennung von from…, to…, parse…, with…, und so weiter. Beachten Sie, dass die java.time- Klassen unveränderlich aufgebaut sind. Einige dieser Namenskonventionen können jedoch auch für veränderbare Klassen hilfreich sein.
Basil Bourque

7

Ein konkretes Beispiel aus einer CAD / CAM-Anwendung.

Ein Schneidpfad würde unter Verwendung eines Konstruktors erstellt. Es ist eine Reihe von Linien und Bögen, die einen zu schneidenden Pfad definieren. Die Reihen von Linien und Bögen können unterschiedlich sein und unterschiedliche Koordinaten haben. Sie können jedoch einfach durch Übergeben einer Liste an einen Konstruktor verarbeitet werden.

Eine Form würde unter Verwendung einer Fabrik hergestellt werden. Denn während es eine Formklasse gibt, wird jede Form je nach Art der Form unterschiedlich eingerichtet. Wir wissen nicht, welche Form wir initialisieren werden, bis der Benutzer eine Auswahl trifft.


5

Angenommen, ich habe einen Konstruktor für eine Klasse, der einen ID-Wert erwartet. Der Konstruktor verwendet diesen Wert, um die Klasse aus der Datenbank zu füllen.

Dieser Prozess sollte definitiv außerhalb eines Konstruktors liegen.

  1. Der Konstruktor sollte nicht auf die Datenbank zugreifen.

  2. Die Aufgabe und der Grund für einen Konstruktor besteht darin, Datenelemente zu initialisieren und eine Klasseninvariante unter Verwendung von Werten zu etablieren, die an den Konstruktor übergeben werden.

  3. Für alles andere ist es besser, die statische Factory-Methode oder in komplexeren Fällen eine separate Factory oder einen Builder zu verwenden Klasse zu verwenden.

Einige Konstruktorrichtlinien von Microsoft :

Führen Sie im Konstruktor nur minimale Arbeit aus. Konstruktoren sollten nur die Konstruktorparameter erfassen. Die Kosten für jede andere Verarbeitung sollten verzögert werden, bis sie erforderlich sind.

Und

Verwenden Sie anstelle eines Konstruktors eine statische Factory-Methode, wenn die Semantik der gewünschten Operation nicht direkt der Konstruktion einer neuen Instanz zugeordnet werden kann.


2

Manchmal müssen Sie beim Erstellen eines Objekts einige Werte / Bedingungen überprüfen / berechnen. Und wenn es eine Ausnahme auslösen kann - Konstruktro ist ein sehr schlechter Weg. Sie müssen also so etwas tun:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

Wo alle zusätzlichen Berechnungen in init () sind. Aber nur Sie als Entwickler wissen wirklich über dieses init () Bescheid. Und natürlich vergisst man es nach Monaten einfach. Aber wenn Sie eine Fabrik haben - tun Sie einfach alles, was Sie brauchen, in einer Methode, indem Sie diese init () vor dem direkten Aufruf verstecken - also keine Probleme. Mit diesem Ansatz ist es kein Problem, auf die Erstellung zu fallen und Speicher zu verlieren.

Jemand hat dir von Caching erzählt. Das ist gut. Sie müssen sich aber auch an das Flyweight-Muster erinnern, das sich gut für die Factory-Methode eignet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.