Я не уверен, какой шаблон проектирования может помочь мне решить эту проблему.
У меня есть класс Coordinator, который определяет, какой класс Worker должен использоваться - без необходимости знать обо всех различных типах Workers - он просто вызывает WorkerFactory и действует на общий интерфейс IWorker.
Затем он устанавливает соответствующий Worker и возвращает результат его метода DoWork.
Это было хорошо ... до сих пор; у нас есть новое требование для нового класса Worker, «WorkerB», для которого требуется дополнительный объем информации, то есть дополнительный входной параметр, для выполнения своей работы.
Это похоже на то, что нам нужен перегруженный метод DoWork с дополнительным входным параметром ... но тогда все существующие работники должны будут реализовать этот метод - что кажется неправильным, поскольку этим рабочим действительно не нужен этот метод.
Как я могу реорганизовать это так, чтобы Координатор не знал, какой работник используется, и при этом позволял каждому работнику получать информацию, необходимую ему для выполнения своей работы, но не заставлять какого-либо работника делать то, что ему не нужно?
Уже существует много рабочих.
Я не хочу менять ни одного из существующих конкретных рабочих, чтобы соответствовать требованиям нового класса WorkerB.
Я подумал, может быть, здесь хорошо подойдет шаблон Decorator, но я не видел, чтобы декораторы украшали объект одним и тем же методом, но разными параметрами раньше ...
Ситуация в коде:
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";
}
}
источник
IWorker
интерфейсе старая версия или это новая версия с добавленным параметром?Coordinator
уже пришлось изменить, чтобы учесть этот дополнительный параметр в своейGetWorkerResult
функции - это означает, что принцип Open-Closed-принцип SOLID нарушается. Как следствие, весь код вызоваCoordinator.GetWorkerResult
должен был быть изменен также. Посмотрите, где вы вызываете эту функцию: как вы решаете, какой IWorker запрашивать? Это может привести к лучшему решению.Ответы:
Вам нужно будет обобщить аргументы так, чтобы они вписывались в один параметр с базовым интерфейсом и переменным количеством полей или свойств. Вроде как это:
Обратите внимание на пустые проверки ... потому что ваша система гибкая и с поздним связыванием, она также не является безопасной для типов, поэтому вам нужно будет проверить свое приведение, чтобы убедиться, что переданные аргументы действительны.
Если вы действительно не хотите создавать конкретные объекты для каждой возможной комбинации аргументов, вы можете вместо этого использовать кортеж (не будет моим первым выбором).
источник
if (args == null) throw new ArgumentException();
Теперь каждый потребитель IWorker должен знать его конкретный тип - и интерфейс бесполезен: вы также можете избавиться от него и использовать вместо этого конкретные типы. И это плохая идея, не правда ли?WorkerFactory.GetWorker
может иметь только один тип возврата). Несмотря на то, что за пределами этого примера мы знаем, что вызывающая сторона может придуматьworkerName
; предположительно, это может привести и к соответствующим аргументам.Я пересмотрел решение на основе комментария @ Dunk:
Поэтому я переместил все возможные аргументы, необходимые для создания IWorker, в метод IWorerFactory.GetWorker, и тогда у каждого работника уже есть то, что ему нужно, и координатор может просто вызвать worker.DoWork ();
источник
Я хотел бы предложить одну из нескольких вещей.
Если вы хотите поддерживать инкапсуляцию, чтобы узлы вызовов не должны были ничего знать о внутренней работе работников или фабрики работников, то вам нужно изменить интерфейс, чтобы иметь дополнительный параметр. Параметр может иметь значение по умолчанию, поэтому некоторые места вызова могут по-прежнему использовать только 2 параметра. Это потребует перекомпиляции любых библиотек-потребителей.
Другой вариант, против которого я бы порекомендовал, так как он нарушает инкапсуляцию и является просто плохим ООП. Это также требует, чтобы вы могли по крайней мере изменить все места вызова для
ConcreteWorkerB
. Вы можете создать класс, который реализуетIWorker
интерфейс, но также имеетDoWork
метод с дополнительным параметром. Затем на ваших сайтах вызовов попытайтесь привести значениеIWorker
with,var workerB = myIWorker as ConcreteWorkerB;
а затем использовать три параметраDoWork
для конкретного типа. Опять же, это плохая идея, но это то, что вы могли бы сделать.источник
@Jtech, вы рассмотрели использование
params
аргумента? Это позволяет передавать переменное количество параметров.https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx
источник