Ich bin mir nicht sicher, welches Entwurfsmuster mir bei der Lösung dieses Problems helfen könnte.
Ich habe eine Klasse, 'Coordinator', die festlegt, welche Worker-Klasse verwendet werden soll - ohne über die verschiedenen Arten von Workern Bescheid wissen zu müssen -, ruft einfach eine WorkerFactory auf und reagiert auf die gemeinsame IWorker-Schnittstelle.
Anschließend setzt es den entsprechenden Worker auf Arbeit und gibt das Ergebnis seiner DoWork-Methode zurück.
Das war in Ordnung ... bis jetzt; Wir haben eine neue Anforderung für eine neue Worker-Klasse, "WorkerB", die eine zusätzliche Menge an Informationen benötigt, dh einen zusätzlichen Eingabeparameter, damit sie ihre Arbeit erledigen kann.
Es ist, als ob wir eine überladene DoWork-Methode mit dem zusätzlichen Eingabeparameter benötigen ... aber dann müssten alle vorhandenen Worker diese Methode implementieren - was falsch erscheint, da diese Worker diese Methode wirklich nicht benötigen.
Wie kann ich dies umgestalten, damit der Koordinator nicht weiß, welcher Mitarbeiter verwendet wird, und jedem Mitarbeiter weiterhin die Informationen zur Verfügung stellt, die er für seine Arbeit benötigt, ohne dass ein Mitarbeiter Dinge tut, die er nicht benötigt?
Es gibt bereits viele existierende Arbeiter.
Ich möchte keinen der vorhandenen konkreten Worker ändern müssen, um den Anforderungen der neuen WorkerB-Klasse gerecht zu werden.
Ich dachte, vielleicht wäre ein Dekorationsmuster hier gut, aber ich habe noch nie gesehen, dass Dekorateure ein Objekt mit derselben Methode, aber unterschiedlichen Parametern dekorieren ...
Situation im Code:
public class Coordinator
{
public string GetWorkerResult(string workerName, int a, List<int> b, string c)
{
var workerFactor = new WorkerFactory();
var worker = workerFactor.GetWorker(workerName);
if(worker!=null)
return worker.DoWork(a, b);
else
return string.Empty;
}
}
public class WorkerFactory
{
public IWorker GetWorker(string workerName)
{
switch (workerName)
{
case "WorkerA":
return new ConcreteWorkerA();
case "WorkerB":
return new ConcreteWorkerB();
default:
return null;
}
}
}
public interface IWorker
{
string DoWork(int a, List<int> b);
}
public class ConcreteWorkerA : IWorker
{
public string DoWork(int a, List<int> b)
{
// does the required work
return "some A worker result";
}
}
public class ConcreteWorkerB : IWorker
{
public string DoWork(int a, List<int> b, string c)
{
// does some different work based on the value of 'c'
return "some B worker result";
}
public string DoWork(int a, List<int> b)
{
// this method isn't really relevant to WorkerB as it is missing variable 'c'
return "some B worker result";
}
}
quelle
IWorker
Schnittstelle in der alten Version aufgeführt oder handelt es sich um eine neue Version mit einem hinzugefügten Parameter?Coordinator
musste bereits geändert werden, um diesen zusätzlichen Parameter in seinerGetWorkerResult
Funktion zu berücksichtigen - das bedeutet, dass das Open-Closed-Prinzip von SOLID verletzt wird. Infolgedessen mussten auch alle CodeaufrufeCoordinator.GetWorkerResult
geändert werden. Schauen Sie sich also den Ort an, an dem Sie diese Funktion aufrufen: Wie entscheiden Sie, welchen IWorker Sie anfordern möchten? Das kann zu einer besseren Lösung führen.Antworten:
Sie müssen die Argumente so verallgemeinern, dass sie in einen einzelnen Parameter mit einer Basisschnittstelle und einer variablen Anzahl von Feldern oder Eigenschaften passen. So ähnlich:
Beachten Sie die Nullprüfungen ... da Ihr System flexibel und spät gebunden ist, ist es auch nicht typsicher. Sie müssen daher Ihre Besetzung überprüfen, um sicherzustellen, dass die übergebenen Argumente gültig sind.
Wenn Sie wirklich nicht für jede mögliche Kombination von Argumenten konkrete Objekte erstellen möchten, können Sie stattdessen ein Tupel verwenden (wäre nicht meine erste Wahl.)
quelle
if (args == null) throw new ArgumentException();
Jetzt muss jeder Verbraucher eines IWorker seinen konkreten Typ kennen - und die Schnittstelle ist nutzlos: Sie können ihn auch entfernen und stattdessen die konkreten Typen verwenden. Und das ist eine schlechte Idee, nicht wahr?WorkerFactory.GetWorker
kann nur einen Rückgabetyp haben). Außerhalb des Bereichs dieses Beispiels wissen wir, dass der Anrufer in der Lage ist, einworkerName
; vermutlich kann es auch entsprechende Argumente liefern.Ich habe die Lösung basierend auf dem Kommentar von @ Dunk überarbeitet:
Daher habe ich alle möglichen Argumente, die zum Erstellen eines IWorker erforderlich sind, in die IWorerFactory.GetWorker-Methode verschoben. Dann hat jeder Worker bereits das, was er benötigt, und der Koordinator kann einfach worker.DoWork () aufrufen.
quelle
Ich würde eines von mehreren Dingen vorschlagen.
Wenn Sie die Kapselung beibehalten möchten, damit die Callsites nichts über das Innenleben der Arbeiter oder der Arbeiterfabrik wissen müssen, müssen Sie die Schnittstelle ändern, um den zusätzlichen Parameter zu erhalten. Der Parameter kann einen Standardwert haben, sodass einige Call-Sites immer noch nur 2 Parameter verwenden können. Dies erfordert, dass alle verbrauchenden Bibliotheken neu kompiliert werden.
Die andere Option würde ich dagegen empfehlen, da sie die Kapselung unterbricht und im Allgemeinen nur eine schlechte OOP ist. Dies erfordert auch, dass Sie mindestens alle Call-Sites für ändern können
ConcreteWorkerB
. Sie können eine Klasse erstellen, die dieIWorker
Schnittstelle implementiert , aber auch eineDoWork
Methode mit einem zusätzlichen Parameter hat. Versuchen Sie dann auf Ihren Callsites, dasIWorker
with zu konvertieren,var workerB = myIWorker as ConcreteWorkerB;
und verwenden Sie dann die drei ParameterDoWork
für den konkreten Typ. Auch dies ist eine schlechte Idee, aber es ist etwas, was Sie tun könnten .quelle
@Jtech, hast du über die Verwendung des
params
Arguments nachgedacht ? Dadurch kann eine variable Anzahl von Parametern übergeben werden.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
quelle