Nie jestem pewien, który wzór może pomóc mi rozwiązać ten problem.
Mam klasę „Koordynator”, która określa, która klasa Worker powinna zostać użyta - bez konieczności znajomości wszystkich różnych rodzajów Workerów - po prostu wywołuje WorkerFactory i działa na wspólnym interfejsie IWorker.
Następnie ustawia odpowiedniego Workera do pracy i zwraca wynik metody „DoWork”.
Było dobrze ... do tej pory; mamy nowy wymóg dotyczący nowej klasy Worker, „WorkerB”, która wymaga dodatkowej ilości informacji, tj. dodatkowego parametru wejściowego, aby mógł on wykonać swoją pracę.
To tak, jakbyśmy potrzebowali przeciążonej metody DoWork z dodatkowym parametrem wejściowym ... ale wtedy wszyscy istniejący pracownicy musieliby zaimplementować tę metodę - co wydaje się błędne, ponieważ ci pracownicy naprawdę nie potrzebują tej metody.
Jak mogę to zmienić, aby Koordynator nie był świadomy, z którego Pracownika korzysta, a jednocześnie pozwalał każdemu Pracownikowi na uzyskanie informacji potrzebnych mu do wykonywania swojej pracy, ale nie pozwalał, aby Żaden Pracownik robił rzeczy, których nie potrzebuje?
Istnieje już wielu pracowników.
Nie chcę zmieniać żadnego z istniejących konkretnych Workerów, aby dostosować je do wymagań nowej klasy WorkerB.
Myślałem, że być może wzór Dekoratora byłby tutaj dobry, ale nie widziałem żadnych Dekoratorów dekorujących obiekt tą samą metodą, ale o różnych parametrach przed ...
Sytuacja w kodzie:
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";
}
}
źródło
IWorker
interfejs zawiera starą wersję, czy jest to nowa wersja z dodanym parametrem?Coordinator
już musiał zostać zmieniony, aby uwzględnić ten dodatkowy parametr w swojejGetWorkerResult
funkcji - oznacza to, że naruszono zasadę otwartego-zamkniętego SOLID. W rezultacie wszystkie wywołania kodu równieżCoordinator.GetWorkerResult
musiały zostać zmienione. Spójrz więc na miejsce, w którym wywołujesz tę funkcję: w jaki sposób decydujesz, którego IWorkera poprosić? Może to prowadzić do lepszego rozwiązania.Odpowiedzi:
Trzeba będzie uogólnić argumenty, aby pasowały do jednego parametru z interfejsem podstawowym i zmienną liczbą pól lub właściwości. Coś w stylu:
Zwróć uwagę na kontrole zerowe ... ponieważ twój system jest elastyczny i spóźniony, nie jest również bezpieczny pod względem typu, więc musisz sprawdzić rzutowanie, aby upewnić się, że przekazane argumenty są poprawne.
Jeśli naprawdę nie chcesz tworzyć konkretnych obiektów dla każdej możliwej kombinacji argumentów, możesz zamiast tego użyć krotki (nie byłbym moim pierwszym wyborem).
źródło
if (args == null) throw new ArgumentException();
Teraz każdy konsument IWorker musi znać swój konkretny typ - a interfejs jest bezużyteczny: możesz go również pozbyć i zamiast tego użyć konkretnych typów. A to zły pomysł, prawda?WorkerFactory.GetWorker
może mieć tylko jeden typ zwrotu). Chociaż poza zakresem tego przykładu, wiemy, że dzwoniący może wymyślićworkerName
; przypuszczalnie może również wymyślić odpowiednie argumenty.Przeprojektowałem rozwiązanie w oparciu o komentarz @ Dunk:
Przesunąłem więc wszystkie możliwe argumenty wymagane do utworzenia IWorkera do metody IWorerFactory.GetWorker, a następnie każdy pracownik ma już to, czego potrzebuje, a Koordynator może po prostu wywołać worker.DoWork ();
źródło
Sugerowałbym jedną z kilku rzeczy.
Jeśli chcesz zachować enkapsulację, aby strony wywoławcze nie musiały nic wiedzieć o wewnętrznych działaniach pracowników lub fabryki pracowników, musisz zmienić interfejs, aby mieć dodatkowy parametr. Parametr może mieć wartość domyślną, dzięki czemu niektóre strony wywoławcze mogą nadal używać tylko 2 parametrów. Będzie to wymagało ponownej kompilacji wszystkich zużywających się bibliotek.
Inną opcję, której odradzam, ponieważ łamie enkapsulację i jest po prostu złym OOP. Wymaga to również, abyś mógł przynajmniej zmodyfikować wszystkie strony wywoławcze
ConcreteWorkerB
. Możesz stworzyć klasę, która implementujeIWorker
interfejs, ale ma takżeDoWork
metodę z dodatkowym parametrem. Następnie w twoich połączeniach próbuj rzutować zaIWorker
pomocą,var workerB = myIWorker as ConcreteWorkerB;
a następnie użyj trzech parametrówDoWork
na konkretny typ. Ponownie jest to zły pomysł, ale można to zrobić.źródło
@Jech, czy zastanawiałeś się nad użyciem
params
argumentu? Pozwala to na przekazanie zmiennej liczby parametrów.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
źródło