No estoy seguro de qué patrón de diseño podría ayudarme a resolver este problema.
Tengo una clase, 'Coordinador', que determina qué clase de Trabajador se debe usar, sin tener que conocer todos los diferentes tipos de Trabajadores que hay, simplemente llama a WorkerFactory y actúa sobre la interfaz común de IWorker.
Luego establece el Trabajador apropiado para trabajar y devuelve el resultado de su método 'DoWork'.
Esto ha estado bien ... hasta ahora; tenemos un nuevo requisito para una nueva clase de Trabajador, "WorkerB", que requiere una cantidad adicional de información, es decir, un parámetro de entrada adicional, para que pueda hacer su trabajo.
Es como si necesitáramos un método DoWork sobrecargado con el parámetro de entrada adicional ... pero todos los trabajadores existentes tendrían que implementar ese método, lo que parece incorrecto ya que esos trabajadores realmente no necesitan ese método.
¿Cómo puedo refactorizar esto para mantener al Coordinador al tanto de qué Trabajador se está utilizando y aún así permitir que cada Trabajador obtenga la información que necesita para hacer su trabajo pero que ningún Trabajador haga cosas que no necesita?
Ya hay muchos trabajadores existentes.
No quiero tener que cambiar ninguno de los Trabajadores concretos existentes para acomodar los requisitos de la nueva clase WorkerB.
Pensé que tal vez un patrón Decorador sería bueno aquí, pero no he visto ningún Decorador decorar un objeto con el mismo método pero con diferentes parámetros antes ...
Situación en el código:
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";
}
}
fuente
IWorker
interfaz muestra la versión anterior o es una nueva versión con un parámetro agregado?Coordinator
ya tuvo que cambiarse para acomodar ese parámetro adicional en suGetWorkerResult
función, eso significa que se viola el Principio de Cerrado Abierto de SOLID. Como consecuencia, todas las llamadas de códigoCoordinator.GetWorkerResult
tuvieron que ser cambiadas también. Mire el lugar donde llama a esa función: ¿cómo decide qué IWorker solicitar? Eso puede conducir a una mejor solución.Respuestas:
Deberá generalizar los argumentos para que quepan en un solo parámetro con una interfaz base y un número variable de campos o propiedades. Algo así como esto:
Tenga en cuenta las verificaciones nulas ... debido a que su sistema es flexible y está sujeto a retraso, tampoco es seguro para escribir, por lo que deberá verificar su conversión para asegurarse de que los argumentos que se pasan sean válidos.
Si realmente no desea crear objetos concretos para cada combinación posible de argumentos, puede usar una tupla en su lugar (no sería mi primera opción).
fuente
if (args == null) throw new ArgumentException();
Ahora cada consumidor de un IWorker debe conocer su tipo concreto, y la interfaz es inútil: también puede deshacerse de él y utilizar los tipos concretos. Y esa es una mala idea, ¿no?WorkerFactory.GetWorker
solo puede tener un tipo de retorno). Si bien está fuera del alcance de este ejemplo, sabemos que la persona que llama puede encontrar unworkerName
; presumiblemente también puede presentar argumentos apropiados.He rediseñado la solución en función del comentario de @ Dunk:
Así que cambié todos los argumentos posibles necesarios para crear un IWorker en el método IWorerFactory.GetWorker y luego cada trabajador ya tiene lo que necesita y el Coordinador puede simplemente llamar a worker.DoWork ();
fuente
Sugeriría una de varias cosas.
Si desea mantener la encapsulación, de modo que los sitios de llamadas no tengan que saber nada sobre el funcionamiento interno de los trabajadores o la fábrica de trabajadores, deberá cambiar la interfaz para tener el parámetro adicional. El parámetro puede tener un valor predeterminado, por lo que algunos sitios de llamadas aún pueden usar solo 2 parámetros. Esto requerirá que las bibliotecas consumidoras se vuelvan a compilar.
La otra opción que recomendaría contra, ya que rompe la encapsulación y generalmente es una mala POO. Esto también requiere que al menos pueda modificar todos los sitios de llamadas
ConcreteWorkerB
. Puede crear una clase que implemente laIWorker
interfaz, pero que también tenga unDoWork
método con un parámetro adicional. Luego, en sus sitios de llamadas, intente lanzar elIWorker
convar workerB = myIWorker as ConcreteWorkerB;
y luego use los tres parámetrosDoWork
en el tipo concreto. Nuevamente, esta es una mala idea, pero es algo que podrías hacer.fuente
@Jtech, ¿has considerado el uso del
params
argumento? Esto permite que se pase una cantidad variable de parámetros.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
fuente