Basisklassen als Fabriken?

14

Ich habe über das Wochenende etwas Code geschrieben und wollte eine Factory als statische Methode in einer Basisklasse schreiben.

Meine Frage ist nur zu wissen, ob dies ein akzeptabler Ansatz ist.

Mein Gefühl, dass dies möglicherweise nicht der Fall ist, beruht auf der Tatsache, dass die Basisklasse Kenntnisse über die abgeleitete Klasse hat.

Trotzdem bin ich mir nicht sicher, wie ich auf einfachere Weise das gleiche Ergebnis erzielen kann. Eine ganze andere Fabrikklasse scheint (zumindest für mich) eine unnötige Komplexität zu sein (?)

Etwas wie:

class Animal
{
  public static Animal CreateAnimal(string name)
  {
     switch(name)
     {
        case "Shark":
          return new SeaAnimal();
          break;
        case "Dog":
          return new LandAnimal();
          break;
        default:
          throw new Exception("unknown animal");
     }
  }
}
class LandAnimal : Animal
{
}

class SeaAnimal : Animal
{
}
Aaron Anodide
quelle
Wie werden Sie Ihre Fabriken testen?
mit dem code in dieser frage würde ich nicht. Aber die Antwort hat mir geholfen, mehr Tests in meinen Codierungsstil zu integrieren
Aaron Anodide
Bedenken Sie, dass es insgesamt schwieriger ist, in einer isolierten Umgebung mit der Seaworld und der Landworld in Ihrer Animal-Klasse umzugehen.

Antworten:

13

Nun, der Vorteil einer separaten Werksklasse ist, dass sie in Unit-Tests verspottet werden kann.

Wenn Sie dies jedoch nicht tun oder auf andere Weise polymorph machen möchten, ist eine statische Factory-Methode für die Klasse selbst in Ordnung.

pdr
quelle
danke, könntest du ein Beispiel dafür geben, worauf du dich beziehst, wenn du polymorph sagst?
Aaron Anodide
Ich meine, wenn Sie jemals in der Lage sein wollen, eine Fabrikmethode durch eine andere zu ersetzen. Das Verspotten zu Testzwecken ist nur eines der häufigsten Beispiele dafür.
pdr
18

Sie können Generics verwenden, um die switch-Anweisung zu umgehen und die nachfolgenden Implementierungen auch von der Basisklasse zu entkoppeln:

 public static T CreateAnimal<T>() where T: new, Animal
 {
    return new T();
 }

Verwendung:

LandAnimal rabbit = Animal.CreateAnimal();  //Type inference should should just figure out the T by the return indicated.

oder

 var rabbit = Animal.CreateAnimal<LandAnimal>(); 
Nick Nieslanik
quelle
2
@PeterK. Fowler bezieht sich auf switch-Anweisungen in Code Smells, aber seine Lösung ist, dass sie in Factory-Klassen extrahiert und nicht ersetzt werden sollen. Das heißt, wenn Sie den Typ zur Entwicklungszeit immer kennen, sind Generika eine noch bessere Lösung. Aber wenn Sie dies nicht tun (wie das ursprüngliche Beispiel andeutet), würde ich argumentieren, dass die Verwendung von Reflexion zur Vermeidung eines Wechsels in einer Fabrikmethode eine falsche Wirtschaftlichkeit sein kann.
pdr
switch-Anweisungen und if-else-if-Ketten sind identisch. Ich benutze die erstere, weil die Kompilierungszeit überprüft, dass der Standardfall behandelt werden muss
Aaron Anodide
4
@PeterK, in einer switch-Anweisung steht der Code an einer einzelnen Stelle. In einem vollständigen OO kann derselbe Code über mehrere separate Klassen verteilt sein. Es gibt eine Grauzone, in der die zusätzliche Komplexität des Vorhandenseins zahlreicher Ausschnitte weniger wünschenswert ist als eine switch-Anweisung.
@Thorbjorn, ich habe genau darüber nachgedacht, aber nie so prägnant, danke, dass du es in Worte
gefasst hast
5

Im Extremfall könnte die Fabrik auch generisch sein.

interface IFactory<K, T> where K : IComparable
{
    T Create(K key);
}

Dann kann man jede Art von Objektfabrik erzeugen, die wiederum jede Art von Objekt erzeugen kann. Ich bin nicht sicher, ob das einfacher ist, es ist sicherlich allgemeiner.

Ich sehe nichts falsches an einer switch-Anweisung für eine kleine Factory-Implementierung. Sobald Sie sich mit einer großen Anzahl von Objekten oder möglichen unterschiedlichen Klassenhierarchien von Objekten befasst haben, ist ein allgemeinerer Ansatz meiner Meinung nach besser geeignet.

Jon Raynor
quelle
1

Schlechte Idee. Erstens verstößt es gegen das Open-Closed-Prinzip. Für jedes neue Tier müsstest du dich erneut mit deiner Basisklasse anlegen und du würdest sie möglicherweise zerstören. Die Abhängigkeiten würden in die falsche Richtung gehen.

Wenn Sie Tiere aus einer Konfiguration erstellen müssen, wäre ein Konstrukt wie dieses in Ordnung, obwohl die Verwendung von Reflektion, um den Typ zu erhalten, der dem Namen entspricht, und dessen Instanziierung unter Verwendung der erhaltenen Typinformationen eine bessere Option wäre.

Sie sollten jedoch trotzdem eine dedizierte Factory-Klasse erstellen, die nicht an die Tierklassenhierarchie gebunden ist, und statt eines Basistyps eine IAnimal-Schnittstelle zurückgeben. Dann wäre es sinnvoll, Sie hätten eine gewisse Entkopplung erreicht.

Martin Maat
quelle
0

Diese Frage ist für mich aktuell - ich habe gestern fast genau solchen Code geschrieben. Ersetzen Sie einfach "Animal" durch das, was in meinem Projekt relevant ist, obwohl ich hier zur Diskussion bei "Animal" bleiben werde. Anstelle einer switch-Anweisung hatte ich eine etwas komplexere Folge von if-Anweisungen, bei denen mehr als nur ein Vergleich einer Variablen mit bestimmten festen Werten durchgeführt wurde. Aber das ist ein Detail. Eine statische Factory-Methode schien ein guter Weg zu sein, um Dinge zu entwerfen, da das Design aus der Umgestaltung früherer schneller und schmutziger Code-Unordnung hervorgegangen ist.

Ich habe diesen Entwurf abgelehnt, weil die Basisklasse Kenntnis von der abgeleiteten Klasse hatte. Wenn die Klassen LandAnimal und SeaAnimal klein, übersichtlich und einfach sind, können sie sich in derselben Quelldatei befinden. Aber ich habe große Probleme beim Lesen von Textdateien, die nicht den offiziellen Standards entsprechen. Ich möchte, dass meine LandAnimal-Klasse in einer eigenen Quelldatei gespeichert wird.

Dies führt zu einer Abhängigkeit von zirkulären Dateien - LandAnimal wird von Animal abgeleitet, Animal muss jedoch bereits wissen, dass LandAnimal, SeaAnimal und fünfzehn weitere Klassen existieren. Ich habe die Factory-Methode rausgezogen und in eine eigene Datei geschrieben (in meiner Hauptanwendung, nicht in meiner Animals-Bibliothek). Eine statische Factory-Methode zu haben schien nett und clever zu sein, aber ich erkannte, dass sie eigentlich kein Designproblem löste.

Ich habe keine Ahnung, wie das mit idiomatischem C # zusammenhängt, da ich oft die Sprache wechsle, ignoriere ich normalerweise Redewendungen und Konventionen, die für Sprachen und Entwicklerstapel außerhalb meiner üblichen Arbeit typisch sind. Wenn überhaupt, könnte mein C # "pythonisch" aussehen, wenn das sinnvoll ist. Ich bemühe mich um allgemeine Klarheit.

Ich weiß auch nicht, ob die Verwendung der statischen Factory-Methode bei kleinen, einfachen Klassen von Vorteil ist, die in der Zukunft wahrscheinlich nicht erweitert werden. In einigen Fällen kann es hilfreich sein, alle Klassen in einer Quelldatei zu haben.

DarenW
quelle