Bester Weg, um Parameter an die Factory-Klasse zu übergeben?

14660
GWLlosa

Ich habe also eine Reihe von Objekten, die ich Impl1, Impl2 und Impl3 nennen werde. Sie implementieren jeweils eine Schnittstelle namens IImpl. Ich habe eine Factory-Klasse, deren Aufgabe es ist, das für einen bestimmten Umstand geeignete ImplX abzurufen und an die Anrufer weiterzuleiten. Der Code in der Fabrik sieht also so aus:

public IImpl GetInstance(params object[] args)
{
    if (args[0]=="Some Value")
         return new IImpl1();
    else if (args[0]=="Other Value")
         return new IImpl2(args[1]);
    else
         return new IImpl3(args[1]);
}

Abhängig von den übergebenen Argumenten werden also verschiedene Instanzen ausgewählt. Alles gut und gut und funktioniert gut. Das Problem ist jetzt, dass ich eine Klasse habe, die diese Factory-Methode aufrufen muss. Es hat keine Verweise auf IImplX, was gut ist, aber es muss letztendlich genau wissen, wie das Eingabearray für GetInstance erstellt wird, um sicherzustellen, dass es die richtige Art von Instanz erhält. Der Code sieht so aus:

switch (_selectedInputEnum)
{
    case InputType.A:
        myIImplInst = Factory.GetInstance("Some Value");
    case InputType.B:
        myIImplInst = Factory.GetInstance("Other Value",this.CollectionB);
    case InputType.C:
        myIImplInst = Factory.GetInstance("Third Value",this.CollectionC);
}

Das fühlt sich sehr überflüssig an und irgendwie aus. Was wäre der beste Weg, die tatsächlichen Parameter der Fabrik zu abstrahieren? Ich fühle mich wie mit der obigen switch-Anweisung, ich bin stark an die Implementierungen von IImplx gekoppelt, auch wenn ich keinen direkten Bezug habe.

Antworten
23
Ich denke, wir brauchen entweder mehr Informationen oder Ihre Erwartungen können nicht erfüllt werden: Wenn Sie verschiedene Parameter an diese Factory übergeben müssen, damit sie ordnungsgemäß funktioniert, muss der Client-Code diese Parameter immer angeben muss also immer wissen, welche Überlast aufgerufen oder welche Parameter angegeben werden sollen. Alex ten Brink vor 9 Jahren 0

5 Antworten auf die Frage

9
tenpn

Wie wäre es mit einer Art Zwischentafel, die der Client-Code vor dem Aufruf der Factory erstellt, und dass jede konkrete Impl eine Abfrage durchführen kann, um sich selbst zu erstellen?

// client code:

Blackboard blackboard = new Blackboard();
blackboard.pushCollectionB(collectionB);
blackboard.pushCollectionC(collectionC);
blackboard.pushFoo(foo); 

IImpl myInstance = Factory.GetInstance(b);

///////////////////////////

// in Factory.GetInstance():

return new Impl3(blackboard);

////////////////////////////

// in Impl3:

Impl3(Blackboard b) { process(b.getCollectionC()); }

Ich habe die switch-Anweisung im Clientcode ausgeblendet, aber Sie könnten dies auch in die Tafel verschieben.

Welche Daten jedes konkrete Impl benötigt, ist nun sowohl vor der Factory als auch vor dem Clientcode verborgen. Wenn Sie jedoch mehr Daten in Ihrem Blackboard für Impl (x + 1) benötigen, müssen Sie alle Stellen in Ihrem Code aktualisieren, durch die ein Blackboard erstellt wird.

Je nach Anwendung kann das Überkonstruieren des Blackboards für Sie teuer sein. Sie könnten beim Start eine zwischengespeicherte Version erstellen oder das Blackboard zu einer Schnittstelle machen und Ihren Client-Code davon ableiten lassen.

In diesem Konstrukt müsste die Fabrik selbst wissen, welche ImplX anzurufen ist, vermutlich durch Abfragen der Tafel selbst? GWLlosa vor 9 Jahren 0
Sie können in beide Richtungen gehen, indem Sie den Schalter im Clientcode für die Übersetzung aus InputType belassen und die Zeichenfolge neben der Tafel in die Factory übergeben oder sie wie gewünscht in die Tafel schieben. So oder so funktioniert es. tenpn vor 9 Jahren 0
7
supercat

Woher kommen CollectionB und CollectionC? Der Ansatz, den ich anstrebe, besteht darin, eine Vielzahl von Fabriken zu haben, die alle dieselbe Art von Eingabe akzeptieren und diese dann in einer Art Sammlung speichern. Wenn zum Beispiel eine Liste von Objekten erstellt werden soll, ein Objekt pro Zeile und der Teil jeder Zeile vor dem ersten Leerzeichen den Typ jedes Elements definiert, könnte man über Fabriken verfügen, die eine Zeichenfolge verwenden und einen entsprechenden Wert ergeben. konfiguriertes Objekt. Wenn die Eingabe beispielsweise so aussah:

Quadrat 0,5,5,29
Zeile 5,2 19,23 6,8
Text 0,29, Hallo da!

Man könnte eine SquareFactory, LineFactory und TextFactory haben, die alle von GraphicObjectFactory erben, die eine Zeichenfolge akzeptieren und GraphicObject zurückgeben. Man könnte dann ein Dictionary haben, das String der GraphicsObjectFactory zuordnet, und eine Instanz jeder der oben genannten Fabriken darin einfügen. Fügen Sie einfach weitere Fabriken zum Wörterbuch hinzu, damit der Datei-Reader mehr Arten von Grafikobjekten verarbeiten kann.

Wenn ich das nicht falsch verstehe, scheint es ein ähnliches Konzept wie Tenpns Antwort zu sein, was nahe an dem liegt, was ich getan habe, also mag ich es :) GWLlosa vor 9 Jahren 0
Es ist ein bisschen wie eine Antwort von tenpn, nur dass seine "Tafel" in verschiedenen Clients der Klasse durch Code konfiguriert wird, während ich davon ausgehen würde, dass die Factory aus einer Konfigurationsdatei oder einem anderen Definitionspunkt erstellt wird. supercat vor 9 Jahren 0
1
Sean Lynch

Es scheint mir, dass die Fabrik den ImplX auf der Basis von arg [1] und nicht von arg [0] wählen sollte.

So etwas würde die Notwendigkeit für den Schalter beseitigen, arg [1] ist jetzt arg [0] in diesem Beispiel:

public IImpl GetInstance(params object[] args)
{
    if (args.length == 0)
         return new IImpl1();
    else if (args[0] is IEnumerable<string>)
         return new IImpl2(args[0]);
    else
         return new IImpl3(args[0]);
}

Man könnte es dann so nennen:

var impl1 = GetInstance();
var impl2 = GetInstance(new List<string>{"1","2"});
var impl3 = GetInstance("");

Bearbeiten: Wenn Sie nicht möchten, dass der Anrufer die inneren Arbeiten des Anrufers kennen muss, sollten Sie Überladungen für getInstance freigeben.

public IImpl GetInstance()
{ 
    return new Impl1();
}
public IImpl GetInstance(IEnumberable<string> strings)
{
    return new Impl2(strings);
}
public IImpl GetInstance(string string)
{
    return new Impl3(string);
}
Voraussetzung dafür ist, dass der Aufrufer von GetInstance (in diesem Fall Ihr zweiter Codeblock) über die internen Funktionen von GetInstance Bescheid weiß (in diesem Fall müssen Sie wissen, dass Sie verschiedene Parameter in unterschiedlichen Reihenfolgen an GetInstance übergeben müssen). GWLlosa vor 9 Jahren 1
Dann denke ich, müssen Sie einige Überschreibungen für GetInstance erstellen, die die verschiedenen aufrufenden Methoden unterstützen. Sean Lynch vor 9 Jahren 0
Müssen sie nicht noch wissen, welche Außerkraftsetzung sie anrufen müssen? GWLlosa vor 9 Jahren 1
Sollen sie entscheiden können, was ImplX sie bekommen? Wenn ja, dann statt GetInstance (IEnumerable strings) wäre GetInstanceImpl2 (IEnumerable Saiten). Sean Lynch vor 9 Jahren 0
1
Homde

Es scheint, als würden Sie das Fabrikmuster missbrauchen. Der ganze Zweck einer Fabrik besteht darin, dass Sie ein allgemeines Objekt übergeben und die Fabrik entscheidet, was für die Rückgabe am besten geeignet ist. Das übergebene Objekt muss nicht über die Fabrik informiert sein, aber die Fabrik sollte über das übergebene Objekt Bescheid wissen.

Wenn Sie sehr explizite Parameter übergeben müssen, ist dies ein Code-Geruch in Ihrer Architektur, und ich würde ernsthaft darüber nachdenken, Refactoring durchzuführen, anstatt dieses Problem nur zu "beheben"

Das ist die genaue Absicht dieser Frage. Umgestaltung in eine bessere Lösung. GWLlosa vor 9 Jahren 1
0
raddevus

Es heißt, diese Frage wurde vor 5 Jahren zum ersten Mal gepostet, aber niemand hat sie jemals beantwortet.

Lassen Sie uns zunächst ein wenig mehr ins Beispiel einführen. Ich schlage vor, wir verwenden eine IPersistable-Schnittstelle, die eine öffentliche Methode namens Save () unterstützt. Es wird wie folgt aussehen:

interface IPersistable {
    bool Save();
}

Als nächstes wollen wir drei Klassen, die diese Methode implementieren:

class FileSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a FILE.");
        return true;
    }
}

class DatabaseSaver : IPersistable{
    public bool Save(){
      Console.WriteLine("I'm saving into a DATABASE.");
      return true;
    }
}

class TcpSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a WEB LOCATION.");
        return true;
    }
}

Schließlich brauchen wir eine Werksklasse, die bei Bedarf unsere entsprechende Klasse aufbaut.

class SaverFactory{
    private string type;
    public SaverFactory(string type){
        this.type = type;
    }

    public IPersistable CreateSaver(){
        switch (type){
            case "FileSaver" :
            {
                return new FileSaver();
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver();
            }
            case "TcpSaver" :
            {
                return new TcpSaver();
            }
            default : 
                return null;
        }
    }
}

Sie können diesen C # -Code jetzt kopieren und in eine LINQPad ( http://linqpad.net ) -Sitzung einfügen und die folgende Hauptmethode hinzufügen. Sie wird dann einwandfrei funktionieren.

void Main()
{
    List<IPersistable> allItems = new List<IPersistable>();
    List<String> fakeData = new List<String>();
    fakeData.Add("FileSaver"); fakeData.Add("DatabaseSaver");

    foreach (String s in fakeData){
        IPersistable ip = new SaverFactory(s).CreateSaver();
        if (ip != null)
            allItems.Add(ip);
    }

    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

Code Run Ergebnis

Wenn Sie diesen Code ausführen, wird Folgendes angezeigt:

Ich speichere in eine Datei.

Ich speichere in eine Datenbank ein.

Das ist eine einwandfrei funktionierende Factory-Methode. Es ist abhängig von der Typzeichenfolge, die Sie übergeben. Dies wird erwartet, weil Sie sagen, dass Sie einen bestimmten Typ erstellen und dass dies erwartet wird.

Was ist, wenn der abhängige Typ Parameter für die Konfiguration benötigt?

Was Sie sich jetzt fragen, ist, was passiert, wenn der Typ, den Sie vom Hersteller erstellen möchten, andere Parameter benötigt, damit er sich selbst konfigurieren kann.

In diesem Fall möchte ich zum Beispiel eine Datenbankverbindung für den DatabaseSaver sowie einen Dateipfad und -namen für den FileSaver und einen URI für den TcpSaver senden. Sie waren der Meinung, dass das Bereitstellen dieser Konfigurationsinformationen für das Erstellen des Implementierungsobjekts Ihren Entwurf irgendwie trübe machte, aber ich glaube nicht, dass dies der Fall ist.

Ich meine, der springende Punkt einer Fabrik ist, dass sie etwas sagt, um eine bestimmte Implementierungsklasse zu erstellen. In meinem Fall erstelle ich eine Liste von Strings, die an das Werk übergeben werden. Stellen Sie sich vor, diese Zeichenketten wurden mit anderen Informationen geladen, die meine IPersistable-Implementierung konfigurieren. Das wäre schön.

Lassen Sie uns das Beispiel etwas näher bringen und zeigen, wie ich das benötigte Konfigurationselement an die Factory übergeben kann, sodass die IPersistable-Implementierungsklasse um diese zusätzlichen Konfigurationsinformationen herum aufgebaut werden kann.

Neue Schnittstelle und Klassen erstellen: IConfigurable

interface IConfigurable{
    String type{get;set;}
}

class FileConfig : IConfigurable{
    public String type{get;set;}
    public string FileName{get;set;}
    public FileConfig(String type, String fileName=null){
        this.type = type;
        FileName = fileName;
    }

}
class DatabaseConfig : IConfigurable{
    public String type{get;set;}
    public string ConnectionString{get;set;}
    public DatabaseConfig(String type, String ConnectionString=null){
        this.type = type;
        this.ConnectionString = ConnectionString;
    }
}

class TcpConfig : IConfigurable{
    public String type{get;set;}
    public string Uri {get;set;}
}

Ich wurde ein wenig faul am letzten und habe das alles nicht umgesetzt, weil ich weiß, dass niemand das alles lesen wird. Ich lasse Ihnen die gesamte Codeliste, die Sie mit LINQPad ausführen und untersuchen können.

void Main()
{
    List<IPersistable> allItems = new List<IPersistable>();
    List<IConfigurable> fakeData = new List<IConfigurable>();
    fakeData.Add(new FileConfig("FileSaver",@"c:\superpath")); 
    fakeData.Add(new DatabaseConfig("DatabaseSaver",@"connection=superdb;integrated security=true"));

    foreach (IConfigurable ic in fakeData){
        IPersistable ip = new SaverFactory(ic).CreateSaver();
        if (ip != null)
            allItems.Add(ip);
    }

    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

interface IPersistable {
    bool Save();
}

interface IConfigurable{
    String type{get;set;}
}

class FileConfig : IConfigurable{
    public String type{get;set;}
    public string FileName{get;set;}
    public FileConfig(String type, String fileName=null){
        this.type = type;
        FileName = fileName;
    }

}
class DatabaseConfig : IConfigurable{
    public String type{get;set;}
    public string ConnectionString{get;set;}
    public DatabaseConfig(String type, String ConnectionString=null){
        this.type = type;
        this.ConnectionString = ConnectionString;
    }
}

class TcpConfig : IConfigurable{
    public String type{get;set;}
    public string Uri {get;set;}
}

class FileSaver : IPersistable{
    IConfigurable config;
    public FileSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a FILE.");
        var configItem = config as FileConfig;
        Console.WriteLine(configItem.FileName);
        return true;
    }
}

class DatabaseSaver : IPersistable{
    IConfigurable config;
    public DatabaseSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a DATABASE.");
        var configItem = config as DatabaseConfig;
        Console.WriteLine(configItem.ConnectionString);
        return true;
    }
}

class TcpSaver : IPersistable{
    IConfigurable config;
    public TcpSaver(IConfigurable config){
        this.config = config;
    }
    public bool Save(){
        Console.WriteLine("I'm saving into a WEB LOCATION.");
        return true;
    }
}

class SaverFactory{
    IConfigurable config;
    public SaverFactory(IConfigurable config){
        this.config = config;
    }

    public IPersistable CreateSaver(){
        switch (config.type){
            case "FileSaver" :
            {
                return new FileSaver(config);
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver(config);
            }
            case "TcpSaver" :
            {
                return new TcpSaver(config);
            }
            default : 
                return null;
        }
    }
}
Ähm, hast du die richtige Frage gestellt? Der von Ihnen überprüfte Code hat nichts mit dem Code in der Frage zu tun. Außerdem gibt es bereits 4 Antworten, also wurde es beantwortet. Tunaki vor 3 Jahren 2
Ich kenne. Ich weiß, dass es 5 Jahre alt ist. Ich dachte, das OP wünschte eine Erklärung des Factory-Musters und wie man es konfigurieren kann. Der ursprüngliche Code von OP machte es für andere sehr schwierig zu antworten und erforderte einige Details dahinter. Mein Beispiel läuft und zeigt, dass Sie Parameter an die Factory senden können, um Ihre Implementierungsklassen zu erstellen, und dass das Senden von Parametern ein gültiger Bestandteil der Verwendung des Factory-Musters sein kann. raddevus vor 3 Jahren 0
Diese alte Frage war zu der Zeit (und mit guten Antworten) ein Fremdwort und ist nicht länger ein Fremdwort, daher ist sie historisch gesperrt. Wenn Sie es mit dem OP mehr besprechen möchten (wenn er / sie das sieht), können Sie es mit in den Chat nehmen. Jamal vor 3 Jahren 0