이 문제를 해결하는 데 어떤 디자인 패턴이 도움이 될지 잘 모르겠습니다.
‘코디네이터’라는 클래스가 있는데, 여기에는 모든 유형의 작업자에 대해 몰라도 어떤 작업자 클래스를 사용해야하는지 결정합니다. 단지 WorkerFactory를 호출하고 공통 IWorker 인터페이스에 작용합니다.
그런 다음 적절한 Worker가 작동하도록 설정하고 ‘DoWork’메서드의 결과를 반환합니다.
이것은 지금까지 훌륭했습니다 … 지금까지; 새로운 Worker 클래스 인 “WorkerB”에 대한 새로운 요구 사항이 있습니다. “WorkerB”에는 작업을 수행하기 위해 추가 정보가 필요합니다. 즉 추가 입력 매개 변수가 필요합니다.
추가 입력 매개 변수가있는 오버로드 된 DoWork 메소드가 필요하지만 … 기존의 모든 작업자는 해당 메소드를 구현해야합니다. 이러한 작업자는 실제로 해당 메소드가 필요하지 않으므로 잘못 보입니다.
코디네이터가 사용중인 작업자를 인식하지 못하고 각 작업자가 자신의 업무를 수행하는 데 필요한 정보를 얻을 수 있지만 작업자가 필요하지 않은 작업을 수행하지 않도록하려면 어떻게 리팩터링 할 수 있습니까?
이미 많은 기존 근로자가 있습니다.
새로운 WorkerB 클래스의 요구 사항을 수용하기 위해 기존의 구체적인 Workers를 변경하고 싶지 않습니다.
데코레이터 패턴이 여기에서 좋을 것이라고 생각했지만 데코레이터가 동일한 방법으로 객체를 장식하는 것을 보지 못했습니다.
코드 상황 :
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";
}
}
답변
기본 인터페이스와 다양한 수의 필드 또는 속성이있는 단일 매개 변수에 맞도록 인수를 일반화해야합니다. 이런 종류의 :
public interface IArgs
{
//Can be empty
}
public interface IWorker
{
string DoWork(IArgs args);
}
public class ConcreteArgsA : IArgs
{
public int a;
public List<int> b;
}
public class ConcreteArgsB : IArgs
{
public int a;
public List<int> b;
public string c;
}
public class ConcreteWorkerA : IWorker
{
public string DoWork(IArgs args)
{
var ConcreteArgs = args as ConcreteArgsA;
if (args == null) throw new ArgumentException();
return "some A worker result";
}
}
public class ConcreteWorkerB : IWorker
{
public string DoWork(IArgs args)
{
var ConcreteArgs = args as ConcreteArgsB;
if (args == null) throw new ArgumentException();
return "some B worker result";
}
}
null 검사는 시스템이 유연하고 늦기 때문에 형식이 안전하지 않으므로 전달 된 인수가 유효한지 캐스트를 확인해야합니다.
가능한 모든 인수 조합에 대해 구체적인 객체를 만들고 싶지 않다면 대신 튜플을 사용할 수 있습니다 (첫 번째 선택이 아닐 수도 있음).
public string GetWorkerResult(string workerName, object args)
{
var workerFactor = new WorkerFactory();
var worker = workerFactor.GetWorker(workerName);
if(worker!=null)
return worker.DoWork(args);
else
return string.Empty;
}
//Sample call
var args = new Tuple<int, List<int>, string>(1234,
new List<int>(){1,2},
"A string");
GetWorkerResult("MyWorkerName", args);
답변
@Dunk의 의견에 따라 솔루션을 다시 설계했습니다.
… IWorker 인스턴스가 생성되기 전에 필요한 모든 매개 변수를 이미 알고 있습니다. 따라서 이러한 인수를 DoWork 메소드가 아닌 생성자에 전달해야합니다. IOW, 팩토리 클래스를 활용하십시오. 인스턴스 구성에 대한 세부 정보를 숨기는 것이 팩토리 클래스가 존재하는 주된 이유입니다.
그래서 IWorker를 만드는 데 필요한 모든 인수를 IWorerFactory.GetWorker 메서드로 옮긴 다음 각 작업자가 이미 필요한 것을 가지고 있으며 코디네이터는 worker.DoWork ();
public interface IWorkerFactory
{
IWorker GetWorker(string workerName, int a, List<int> b, string c);
}
public class WorkerFactory : IWorkerFactory
{
public IWorker GetWorker(string workerName, int a, List<int> b, string c)
{
switch (workerName)
{
case "WorkerA":
return new ConcreteWorkerA(a, b);
case "WorkerB":
return new ConcreteWorkerB(a, b, c);
default:
return null;
}
}
}
public class Coordinator
{
private readonly IWorkerFactory _workerFactory;
public Coordinator(IWorkerFactory workerFactory)
{
_workerFactory = workerFactory;
}
// Adding 'c' breaks Open/Closed principal for the Coordinator and WorkerFactory; but this has to happen somewhere...
public string GetWorkerResult(string workerName, int a, List<int> b, string c)
{
var worker = _workerFactory.GetWorker(workerName, a, b, c);
if (worker != null)
return worker.DoWork();
else
return string.Empty;
}
}
public interface IWorker
{
string DoWork();
}
public class ConcreteWorkerA : IWorker
{
private readonly int _a;
private readonly List<int> _b;
public ConcreteWorkerA(int a, List<int> b)
{
_a = a;
_b = b;
}
public string DoWork()
{
// does the required work based on 'a' and 'b'
return "some A worker result";
}
}
public class ConcreteWorkerB : IWorker
{
private readonly int _a;
private readonly List<int> _b;
private readonly string _c;
public ConcreteWorkerB(int a, List<int> b, string c)
{
_a = a;
_b = b;
_c = c;
}
public string DoWork()
{
// does some different work based on the value of 'a', 'b' and 'c'
return "some B worker result";
}
}
답변
여러 가지 중 하나를 제안합니다.
호출 사이트가 작업자 또는 작업자 공장의 내부 작업에 대해 알 필요가 없도록 캡슐화를 유지하려면 추가 매개 변수를 갖도록 인터페이스를 변경해야합니다. 매개 변수는 기본값을 가질 수 있으므로 일부 호출 사이트는 여전히 2 개의 매개 변수 만 사용할 수 있습니다. 이를 위해서는 소비되는 모든 라이브러리를 다시 컴파일해야합니다.
캡슐화를 깨뜨리고 일반적으로 나쁜 OOP이므로 권장하지 않는 다른 옵션입니다. 또한에 대한 모든 호출 사이트를 최소한 수정할 수 있어야합니다 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