Não tenho certeza de qual padrão de design pode me ajudar a resolver esse problema.
Eu tenho uma classe, 'Coordinator', que determina qual classe Worker deve ser usada - sem ter que saber sobre todos os diferentes tipos de Workers que existem - ela apenas chama um WorkerFactory e atua na interface comum do IWorker.
Em seguida, define o Worker apropriado para trabalhar e retorna o resultado do método 'DoWork'.
Isso tem sido bom ... até agora; temos um novo requisito para uma nova classe Worker, "WorkerB", que requer uma quantidade adicional de informações, isto é, um parâmetro de entrada adicional, para que ele faça seu trabalho.
É como se precisássemos de um método DoWork sobrecarregado com o parâmetro de entrada extra ... mas todos os Trabalhadores existentes teriam que implementar esse método - o que parece errado, pois esses Trabalhadores realmente não precisam desse método.
Como posso refatorar isso para manter o coordenador inconsciente de qual trabalhador está sendo usado e ainda permitir que cada trabalhador obtenha as informações necessárias para realizar seu trabalho, mas que nenhum trabalhador faça o que não é necessário?
Já existem muitos Trabalhadores.
Não quero alterar nenhum dos Trabalhadores concretos existentes para acomodar os requisitos da nova classe WorkerB.
Eu pensei que talvez um padrão Decorator seria bom aqui, mas eu não vi nenhum Decorator decorar um objeto com o mesmo método, mas com parâmetros diferentes antes ...
Situação no 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";
}
}
fonte
IWorker
interface está listada na versão antiga ou é uma nova versão com um parâmetro adicionado?Coordinator
já tinha que ser alterado para acomodar esse parâmetro extra em suaGetWorkerResult
função - isso significa que o princípio de aberto-fechado do SOLID é violado. Como conseqüência, todas as chamadas de código tambémCoordinator.GetWorkerResult
precisavam ser alteradas. Portanto, observe o local em que você chama essa função: como você decide qual IWorker solicitar? Isso pode levar a uma solução melhor.Respostas:
Você precisará generalizar os argumentos para que eles se ajustem a um único parâmetro com uma interface base e um número variável de campos ou propriedades. Mais ou menos assim:
Observe as verificações nulas ... porque seu sistema é flexível e com ligação tardia, também não é do tipo seguro; portanto, você precisará verificar sua conversão para garantir que os argumentos transmitidos sejam válidos.
Se você realmente não deseja criar objetos concretos para todas as combinações possíveis de argumentos, use uma tupla (não seria minha primeira escolha).
fonte
if (args == null) throw new ArgumentException();
Agora, todo consumidor de um IWorker deve conhecer seu tipo de concreto - e a interface é inútil: você também pode se livrar dele e usar os tipos de concreto. E isso é uma péssima ideia, não é?WorkerFactory.GetWorker
pode ter apenas um tipo de retorno). Enquanto estiver fora do escopo deste exemplo, sabemos que o chamador pode criar umworkerName
; presumivelmente, ele também pode apresentar argumentos apropriados.Eu reprojetei a solução com base no comentário de @ Dunk:
Então, mudei todos os argumentos possíveis necessários para criar um IWorker no método IWorerFactory.GetWorker e, em seguida, cada trabalhador já possui o que precisa e o Coordenador pode chamar apenas worker.DoWork ();
fonte
Eu sugeriria uma de várias coisas.
Se você deseja manter o encapsulamento, para que os callites não precisem saber nada sobre o funcionamento interno dos trabalhadores ou da fábrica de trabalhadores, será necessário alterar a interface para obter o parâmetro extra. O parâmetro pode ter um valor padrão, para que alguns locais de chamada ainda possam usar apenas 2 parâmetros. Isso exigirá que todas as bibliotecas consumidoras sejam recompiladas.
A outra opção que eu recomendaria, uma vez que quebra o encapsulamento e geralmente é um POO ruim. Isso também requer que você possa pelo menos modificar todos os chamados
ConcreteWorkerB
. Você pode criar uma classe que implementa aIWorker
interface, mas também possui umDoWork
método com um parâmetro extra. Em seus callites, tente converter oIWorker
withvar workerB = myIWorker as ConcreteWorkerB;
e use o parâmetro threeDoWork
no tipo concreto. Novamente, essa é uma péssima idéia, mas é algo que você poderia fazer.fonte
@Jtech, você considerou o uso do
params
argumento? Isso permite que uma quantidade variável de parâmetros seja passada.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
fonte