[Design pattern for C#] 역할 사슬 모델

역할 사슬 모델(Chain of responsibility)은 보통 윈도우 GUI의 이벤트 처리를 위한 용도로 많이 쓰이는 디자인 패턴입니다.
구현 방법이야 여러 가지가 있겠습니다만, 기본적으로는 어떤 메시지를 지정된 핸들러들에게 전달해서, 처리할 수 있는 핸들러만 메시지를 처리하고 그렇지 못한 핸들러는 메시지를 무시하는 원리로 동작합니다.

아래 세 가지 구현을 예로 보여드립니다. 같은 내용을 영문 위키피디아(http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern) 에도 올려놓았습니다. 한국 위키피디아에는 아쉽게 해당 토픽이 없네요.

추상 클래스 기반 구현

using System;

namespace Chain_of_responsibility
{
    public abstract class Chain
    {
        private Chain _next;

        public Chain Next
        {
            get
            {
                return _next;
            }
            set
            {
                _next = value;
            }
        }

        public void Message(object command)
        {
            if ( Process(command) == false && _next != null )
            {
                _next.Message(command);
            }
        }

        public static Chain operator +(Chain lhs, Chain rhs)
        {
            Chain last = lhs;

            while ( last.Next != null )
            {
                last = last.Next;
            }

            last.Next = rhs;

            return lhs;
        }

        protected abstract bool Process(object command);
    }

    public class StringHandler : Chain
    {
        protected override bool Process(object command)
        {
            if ( command is string )
            {
                Console.WriteLine("StringHandler can handle this message : {0}",(string)command);

                return true;
            }

            return false;
        }
    }

    public class IntegerHandler : Chain
    {
        protected override bool Process(object command)
        {
            if ( command is int )
            {
                Console.WriteLine("IntegerHandler can handle this message : {0}",(int)command);

                return true;
            }

            return false;
        }
    }

    public class NullHandler : Chain
    {
        protected override bool Process(object command)
        {
            if ( command == null )
            {
                Console.WriteLine("NullHandler can handle this message.");

                return true;
            }

            return false;
        }
    }

    public class IntegerBypassHandler : Chain
    {
        protected override bool Process(object command)
        {
            if ( command is int )
            {
                Console.WriteLine("IntegerBypassHandler can handle this message : {0}",(int)command);

                return false; // Always pass to next handler
            }

            return false; // Always pass to next handler
        }
    }

    class TestMain
    {
        static void Main(string[] args)
        {
            Chain chain = new StringHandler();
            chain += new IntegerBypassHandler();
            chain += new IntegerHandler();
            chain += new IntegerHandler(); // Never can reached
            chain += new NullHandler();

            chain.Message("1st string value");
            chain.Message(100);
            chain.Message("2nd string value");
            chain.Message(4.7f); // not handled
            chain.Message(null);
        }
    }
}

추상 클래스 기반으로 구현하는 방법입니다. 하지만 이 방식은 썩 좋지 않은 방식입니다. 일단 클래스 상속을 꼭 해야 되기 때문에 파생 클래스를 핸들러로 등록할 수 없습니다.
그 외에 ArrayList라는 좋은 컬렉션 클래스가 있는데 굳이 링크드리스트를 구현해서 사용하고 있는 것도 눈여겨 보아 주시기 바랍니다.

인터페이스 기반 구현

using System;
using System.Collections;

namespace Chain_of_responsibility
{
    public interface IChain
    {
        bool Process(object command);
    }

    public class Chain
    {
        private ArrayList _list;

        public ArrayList List
        {
            get
            {
                return _list;
            }
        }

        public Chain()
        {
            _list = new ArrayList();
        }

        public void Message(object command)
        {
            foreach ( IChain item in _list )
            {
                bool result = item.Process(command);

                if ( result == true ) break;
            }
        }

        public void Add(IChain handler)
        {
            List.Add(handler);
        }
    }

    public class StringHandler : IChain
    {
        public bool Process(object command)
        {
            if ( command is string )
            {
                Console.WriteLine("StringHandler can handle this message : {0}",(string)command);

                return true;
            }

            return false;
        }
    }

    public class IntegerHandler : IChain
    {
        public bool Process(object command)
        {
            if ( command is int )
            {
                Console.WriteLine("IntegerHandler can handle this message : {0}",(int)command);

                return true;
            }

            return false;
        }
    }

    public class NullHandler : IChain
    {
        public bool Process(object command)
        {
            if ( command == null )
            {
                Console.WriteLine("NullHandler can handle this message.");

                return true;
            }

            return false;
        }
    }

    public class IntegerBypassHandler : IChain
    {
        public bool Process(object command)
        {
            if ( command is int )
            {
                Console.WriteLine("IntegerBypassHandler can handle this message : {0}",(int)command);

                return false; // Always pass to next handler
            }

            return false; // Always pass to next handler
        }
    }

    class TestMain
    {
        static void Main(string[] args)
        {
            Chain chain = new Chain();

            chain.Add(new StringHandler());
            chain.Add(new IntegerBypassHandler());
            chain.Add(new IntegerHandler());
            chain.Add(new IntegerHandler()); // Never can reached
            chain.Add(new NullHandler());

            chain.Message("1st string value");
            chain.Message(100);
            chain.Message("2nd string value");
            chain.Message(4.7f); // not handled
            chain.Message(null);
        }
    }
}

추상 클래스 구현보다 좀 더 유연해졌습니다. 인터페이스를 기반으로 구현했기 때문에 파생 클래스를 핸들러로 등록할 수도 있게 되었습니다. 또한 ArrayList라는 컬렉션을 사용했지요.

델리게이트와 이벤트를 사용한 구현

using System;

namespace Chain_of_responsibility
{
    public class StringHandler
    {
        private static int count;

        public void Process(object command)
        {
            if ( command is string )
            {
                string th;
                count++;

                if ( count % 10 == 1 ) th = "st";
                else if ( count % 10 == 2 ) th = "nd";
                else if ( count % 10 == 3 ) th = "rd";
                else th = "th";

                Console.WriteLine("StringHandler can handle this {0}{1} message : {2}",count,th,(string)command);
            }
        }
    }

    public class StringHandler2
    {
        public void Process(object command)
        {
            if ( command is string )
            {
                Console.WriteLine("StringHandler2 can handle this message : {0}",(string)command);
            }
        }
    }

    public class IntegerHandler
    {
        public void Process(object command)
        {
            if ( command is int )
            {
                Console.WriteLine("IntegerHandler can handle this message : {0}",(int)command);
            }
        }
    }

    class Chain
    {
        public delegate void MessageHandler(object message);
        public static event MessageHandler Message;

        public static void Process(object message)
        {
            Message(message);
        }
    }

    class TestMain
    {
        static void Main(string[] args)
        {
            StringHandler sh1 = new StringHandler();
            StringHandler2 sh2 = new StringHandler2();
            IntegerHandler ih = new IntegerHandler();

            Chain.Message += new Chain.MessageHandler(sh1.Process);
            Chain.Message += new Chain.MessageHandler(sh2.Process);
            Chain.Message += new Chain.MessageHandler(ih.Process);
            Chain.Message += new Chain.MessageHandler(ih.Process); // Can also reached

            Chain.Process("1st string value");
            Chain.Process(100);
            Chain.Process("2nd string value");
            Chain.Process(4.7f); // not handled
        }
    }
}

이 방법이 C#에서 이벤트 처리를 위해 주로 사용하는 방식이라 할 수 있습니다.
인터페이스조차 사용하지 않았지요.
델리게이트는 일종의 함수 포인터와 비슷한 개념인데, 좀 더 객체지향적이고 좀 더 안전한 타입입니다. 이벤트는 델리게이트를 좀 더 편하게 사용하기 위한 C# 언어 차원의 배려이고요. 이벤트 키워드 없이 델리게이트만으로도 위의 코드를 사용할 수 있습니다.


크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 운명과시간의신

2008/04/22 23:17 2008/04/22 23:17
, , , , ,
Response
No Trackback , No Comment
RSS :
http://rafi.inha.ac.kr/~wbstory/blog/rss/response/85

Trackback URL : http://rafi.inha.ac.kr/~wbstory/blog/trackback/85

Leave a comment
[로그인][오픈아이디란?]
« Previous : 1 : ... 31 : 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : ... 118 : Next »

블로그 이미지

게임 개발자가 되고 싶은 한 프로그래머의 블로그입니다.

- 운명과시간의신

Archives

Calendar

«   2010/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
16003
Today:
4
Yesterday:
11