ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 닷넷 3.5를 이용한 디자인 패턴의 구현 (7)
    DesignPatterns 2009. 8. 18. 02:57
    반응형


    Chain-of-Command 패턴

    이 패턴은 명령 오브젝트들과 처리 오브젝트들을 별도로 분리할 수 있고, 연속된 처리를 해야 하는 상황에서 이를 실행되는 장소와 기능 모두를 가질 수 있는 오브젝트를 만들 수 있도록 해주는 매우 강력한 디자인 패턴이다. 그래서, 워크플로우나 연속적으로 발생하는 상황을 구현하는 데 매우 유용하다.
    하지만, Chain-of-Command 패턴은 주의 깊게 사용해야 한다. 왜냐하면, 절차적인 프로그래밍을 하다가 객체지향 프로그래밍으로 갈아타는 개발자들은 단 하나의 명령 오브젝트와 수없이 많은 처리 오브젝트들을 만들어 낸다. 이러한 부분들은 객체지향 방식을 사용으로 바꿔야 할 곳인데 습관적으로 반복되는 불필요한 절차적인 코딩을 계속 반복하게 될 경우가 많다

    각각의 처리 오브젝트는 자신이 처리할 수 있는 명령 오브젝트들의 타입과 처리할 수 없을 경우에 연결된 다음 처리 오브젝트로 넘겨주기  위한 로직을 포함하고 잇다. 그래서, 각 오브젝트는 깔끔하게 캡슐화되고 단 하나의 잘 정의된 책임(responsibility)들의 집합을 갖게 된다.

    이 패턴을 확인하기 위해서는 확장을 해야 한다. 즉, 연결된 체인의 끝에 새로운 처리 오브젝트들을 추가하기 위한 과정이 있어야 한다.

    잘 구현된 Chain-of-Command 패턴은 느슨한 결합을 증진시킨다. 이러한 느슨한 결합은 닷넷 3.5가 추구하고 지속 가능한 소프트웨어에 많이 사용하는 n-티어 프로그래밍에서는 필수적이다

    예제로 알아보는 Chain-of-Command 패턴

    이 패턴을 설명하기 위해서, 우주선의 이륙을 허가하는 예제를 최대한 단순하게 사용할 것이다. 

    Responsibility 트리구조
    이 패턴을 두 가지 서브 타입으로 나눌 수 있는 처리기들을 가진 좀 더 진보적으로 발전 시킨 변형 패턴이 있다. 하나는 자신에게 필요한 것은 스스로 해결하는 것이고  다른 하나는 필요한 조건들을 다른 처리기에 전달(dispatch)하는 것이다 .이 변형 패턴은 명령의 연속(chain)이라기보다는 책임(responsibility)의 계층 구조를 만들어 내는데, 규모가 큰 프로그램에서는 매우 복잡해진다

    전달 클래스(dispatcher class)들은 자신  스스로에게 처리를 넘기거나, 오브젝트 A가 다른 오브젝트에게 처리를 넘겼는데 결과적으로 오브젝트 A로 되돌아 온다면, 이계층 구조는 재귀적이 될 수 잇다. 물론, 재귀 호출을 조심스럽게 다룰 수 있다면, 꼭 문제가 되는 상황은 아니다. 예를 들어, 이러한 로직이 끝나는 지점과 계속 반복되지 않기 위해서 충분히 제한 사항을 둔 재귀 구조일 경우에는 괜찮을 수 도 있다.
    XML 파서처럼 매우 성공적인 재귀 구조도 있다.

    Chain-of-Command 패턴을 보여주는 UML 클래스 다이어그램


    이번 우주선 이륙이 가능하게 하는 필요 조건들은 다음과같다.

    - 반드시 세명의 승무원이 있어야 한다.
    - 우주선에 장착된 백만 파운드의 연료가 있어야 한다.
    - 모두 세 명의 실행 명령 구너한자가 올바른 순서로 "이륙" 명령을 주어야 한다.

    이번 예제는 , 다음처럼 각 이벤트를 넘겨주기 위해서 LaunchRequestEventArgs 오브젝트를 만들 것이다. 여기에는 이벤트를 다루는 데 필요한 모든 정보, 즉 승무원 숫자, 장착된 연료의양, launchCommandRequest문자열 변수를 사용하는 이륙 명령 등이 포함될 것이다

        public class LaunchRequestEventArgs : EventArgs
        {
            //프로퍼티들
            public int Crew { get; set; }
            public string LaunchCommand { get; set; }
            public double FuelOnBoardInLbs { get; set; }

            // 생성자
            public LaunchRequestEventArgs(int crewCount, double fuelOnBoard, string launchCommandRequest)
            {
                this.Crew = crewCount;
                this.FuelOnBoardInLbs = fuelOnBoard;
                this.LaunchCommand = launchCommandRequest;
            }
        }

    여기에 다름처럼 Approver와 LaunchRequestEventArgs의 오브젝트 타입을 지정할 제너릭 델리게이트를 만든다.

      public delegate void LaunchRequestEventHandler<T, U>(T sender, U eventArgs);


    조종사들고 또 다른 명령권자를 지정할 LaunchRequestEventHandler 같은 이벤트 처리기에서 사용할 특정 타입의 이벤트들을 만들기 위해 이 델리게이트를 사용할 것이다. 그렇게 하기 위해서, 다음처럼 공통 기반 클래스로 사용할 추상 클래스(Approver)를 만들어 OOP의 다형성(Polymorphism)을 이용할 것이다.


    //이벤트 처리기(추상클래스)
        abstract class Approver
        {
            public Approver Successor { get; set; }

            //이벤트
            public event LaunchRequestEventHandler<Approver, LaunchRequestEventArgs> Request;

            // 이륙 오청 이벤트를 일으킨다
            public virtual void OnRequest(LaunchRequestEventArgs e)
            {
                if (Request != null)
                {
                    Request(this, e);
                }
            }

            public void ProcessRequest(Request request)
            {
                OnRequest(new LaunchRequestEventArgs(request.Crew, request.FuelOnBoardInLbs, request.LaunchCommand));
            }
        }


    이제 ,Approver 타입을 상속받아 자신만의 이벤트를 정의한 파생 클래스를 만들 준비가 되었다.


    //실제 이벤트 처리기

        class Pilot : Approver
        {
            //생성자
            public Pilot()
            {
                // 이벤트의 델리게이트 등록
                this.Request += new LaunchRequestEventHandler<Approver, LaunchRequestEventArgs>(PilotRequest);
            }

            void PilotRequest(Approver sender, LaunchRequestEventArgs e)
            {
                if (e.Crew < 3)
                {
                    Console.WriteLine("{0}, you are only reporting {1} crew on board.", this.GetType().Name, e.Crew);
                    Console.WriteLine("We need at least 3. {0} denied. \n\n", e.LaunchCommand);
                }
                else if (Successor != null)
                {
                    Console.WriteLine("{0} : Commander says: {1} Go. \n\n", e.LaunchCommand, this.GetType().Name);
                    Successor.OnRequest(e);
                }
            }
        }

    PilotRequest의 로직은 다음고 같다. 승무원의 수가 세 명보다 적으면, 파일럿은 요청을 거부한다. 그렇지 않고, 출발 명령 계통상에 다른 승인자가 있다면, 파일럿은 "이륙" 명령을 내린다.

    Commander 클래스는 충분한 연료를 체크하는 조건을 제외하면 거의 동일하다.

    // 실제 이벤트 처리기
        class Commander : Approver
        {
            //생성자
            public Commander()
            {
                //이벤트의 델리게이트 등록
                this.Request += new LaunchRequestEventHandler<Approver, LaunchRequestEventArgs>(CommanderRequest);
            }

            void CommanderRequest(Approver sender, LaunchRequestEventArgs e)
            {
                if (e.FuelOnBoardInLbs < 1000000.0)
                {
                    //에러를 보고 한다
                }
                else if (Successor != null)
                {
                    // Go 명령을 다른 명령 선상의 명령권자에게 넘긴다
                }
            }
        }



    다음은 완전히 구현한 예제이다



    실행시킨 결과는 다음과 같다





    이 우주선의 비행은 승무원 숫자나 충분한 연료 등의 필요한 조건들이 충족되고 세 명의 명령권자들과 비행 총책임자(FlightDirector)의 순차적인 이륙 명령이 있어야 실제로 이륙할 수 있다.





    - 참고  Programming .Net 3.5 by Jesse Liberty and Alex Horovitz.
    반응형

    댓글

Designed by Tistory.