입력 매개 변수가 다른 작업자를위한 C # 디자인 패턴


14

이 문제를 해결하는 데 어떤 디자인 패턴이 도움이 될지 잘 모르겠습니다.

'코디네이터'라는 클래스가 있는데, 여기에는 모든 유형의 작업자에 대해 몰라도 어떤 작업자 클래스를 사용해야하는지 결정합니다. 단지 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";
    }    
}

는 IS IWorker인터페이스는 이전 버전을 나열하거나이 추가 매개 변수와 함께 새로운 버전인가?
JamesFaix

현재 2 개의 매개 변수와 함께 IWorker를 사용중인 코드베이스의 위치가 3 번째 매개 변수를 연결해야합니까, 아니면 새 호출 사이트 만 3 번째 매개 변수를 사용합니까?
JamesFaix

2
패턴을 구매 하지 않고 패턴 적용 여부에 관계없이 전체 디자인에 집중하십시오 . 권장 독서 : “패턴 쇼핑”형 질문은 얼마나 나쁘습니까?

1
코드에 따르면 IWorker 인스턴스를 만들기 전에 필요한 모든 매개 변수를 이미 알고 있습니다. 따라서 이러한 인수를 DoWork 메소드가 아닌 생성자에 전달해야합니다. IOW, 팩토리 클래스를 활용하십시오. 인스턴스 구성에 대한 세부 정보를 숨기는 것이 팩토리 클래스가 존재하는 주된 이유입니다. 그 접근법을 취했다면 해결책은 간단합니다. 또한 달성하려는 방식으로 달성하려는 것은 나쁜 OO입니다. Liskov 대체 원칙을 위반합니다.
Dunk

1
다른 수준으로 돌아 가야한다고 생각합니다. Coordinator해당 GetWorkerResult기능 에 해당 추가 매개 변수를 수용하도록 이미 변경해야 했습니다. 즉, SOLID의 공개 폐쇄 원칙을 위반했음을 의미합니다. 결과적으로 모든 코드 호출 Coordinator.GetWorkerResult도 변경되어야했습니다. 따라서 해당 함수를 호출 한 장소를 살펴보십시오. 어떤 IWorker를 요청할지 어떻게 결정합니까? 더 나은 솔루션으로 이어질 수 있습니다.
Bernhard Hiller

답변:


9

기본 인터페이스와 다양한 수의 필드 또는 속성이있는 단일 매개 변수에 맞도록 인수를 일반화해야합니다. 이런 종류의 :

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);

1
이것은 Windows Forms 응용 프로그램이 이벤트를 처리하는 방식과 유사합니다. "args"매개 변수 1 개 및 "이벤트 소스"매개 변수 1 개 모든 "args"는 EventArgs에서 하위 클래스로 분류됩니다. msdn.microsoft.com/en-us/library/…- >이 패턴은 매우 효과적입니다. 난 그냥 "튜플"제안을 좋아하지 않습니다.
Machado

if (args == null) throw new ArgumentException();이제 IWorker의 모든 소비자는 콘크리트 유형을 알아야합니다. 인터페이스는 쓸모가 없습니다. 인터페이스를 제거하고 콘크리트 유형을 대신 사용할 수 있습니다. 그리고 그것은 나쁜 생각입니까?
Bernhard Hiller

플러그 가능 아키텍처로 인해 IWorker 인터페이스가 필요합니다 ( WorkerFactory.GetWorker반환 유형은 하나만 가질 수 있음). 이 예제의 범위를 벗어나는 동안 호출자가 workerName; 아마도 그것은 또한 적절한 논증을 내놓을 수 있습니다.
John Wu

2

@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";
        }
    }

1
모든 상황에서 3 개를 모두 사용하지는 않지만 3 개의 매개 변수를받는 팩토리 메소드가 있습니다. 더 많은 매개 변수가 필요한 객체 C가 있다면 어떻게 할 것입니까? 메소드 서명에 추가 하시겠습니까? 이 솔루션은 IMO 확장 및 병 권고하지 않습니다
아모 피스

3
더 많은 인수가 필요한 새로운 ConcreteWorkerC가 필요하다면 예, GetWorker 메소드에 추가됩니다. 그렇습니다. 공장은 공개 / 폐쇄 교장을 준수하지 않지만 어딘가에 이런 것이 있어야하며 공장은 최선의 선택이었습니다. 내 제안은 : 이것이 잘못되었다고 말하는 것이 아니라 실제로 대안 솔루션을 게시하여 커뮤니티를 도울 것입니다.
JTech

1

여러 가지 중 하나를 제안합니다.

호출 사이트가 작업자 또는 작업자 공장의 내부 작업에 대해 알 필요가 없도록 캡슐화를 유지하려면 추가 매개 변수를 갖도록 인터페이스를 변경해야합니다. 매개 변수는 기본값을 가질 수 있으므로 일부 호출 사이트는 여전히 2 개의 매개 변수 만 사용할 수 있습니다. 이를 위해서는 소비되는 모든 라이브러리를 다시 컴파일해야합니다.

캡슐화를 깨뜨리고 일반적으로 나쁜 OOP이므로 권장하지 않는 다른 옵션입니다. 또한에 대한 모든 호출 사이트를 최소한 수정할 수 있어야합니다 ConcreteWorkerB. IWorker인터페이스 를 구현하는 클래스를 만들 수 있지만 DoWork추가 매개 변수 가있는 메서드 도 있습니다 . 그런 다음 호출 사이트에서 IWorkerwith 를 캐스트 한 var workerB = myIWorker as ConcreteWorkerB;다음 DoWork콘크리트 유형에 세 개의 매개 변수를 사용하십시오 . 다시 말하지만 이것은 나쁜 생각이지만 할 수있는 일입니다.


0

@Jtech, 당신은 params논쟁 의 사용을 고려 했습니까? 이를 통해 가변 량의 매개 변수를 전달할 수 있습니다.

https://msdn.microsoft.com/en-us/library/w5zay9db(v=vs.71).aspx


params 키워드는 DoWork 메소드가 각 인수에 대해 동일한 작업을 수행하고 각 인수가 동일한 유형 인 경우 의미가 있습니다. 그렇지 않으면 DoWork 메소드는 params 배열의 각 인수가 올바른 유형인지 확인해야합니다. 그러나 두 개의 문자열이 있고 각 문자열이 다른 목적으로 사용되었다고 가정하면 DoWork가 올바른지 확인하는 방법은 무엇입니까? 하나 ... 배열의 위치를 ​​기반으로 가정해야합니다. 내 취향에 너무 느슨합니다. @JohnWu의 솔루션이 더 빡빡하다고 생각합니다.
JTech
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.