C#

[C#][MSDN] C# Event 발행자(publisher)와 구독자(subscriber) 방식, 옵저버 패턴에 대해

Binceline 2016. 8. 21. 21:42

처음엔 옵저버 패턴과 같은 줄 알았는데, 아니었다.(비슷함)


다음은 옵저버 패턴의 UML이다.




위의 구조를 보면, 옵저버 패턴에서는 notifyObservers() 함수 내에서 루프를 돌며(물론 모든 옵저버 패턴이 무조건 루프 방식은 아닐 수도 있지만) 


모든 옵저버들의 내용을 갱신한다.


파생 옵저버들(이런저런 구독자들이라 할 수 있음)은 Observer 클래스를 부모로 가지고 있어야 하며, 꼭 Subject 클래스 내부에서 어떤 식으로든 참조할 수 있어야 한다.


하지만 C# Event 발행자/구독자 방식은 조금 다르다.


발행자는 구독자에게 자신의 이벤트 처리 방식을 넣어 준다.


즉, 구독자는 그저 자신이 어떤 행동을 했을 때 원하는 결과가 나오도록 해 주는 발행자를 선택하면 되는 것이다.


그래서 발행자는 구독자에 대한 리스트를 가질 필요가 없다.


하지만 구독자 리스트를 가지고 뭔가를 하는 순간부터 일반적인 옵저버 패턴과 같아진다.



마우스.좌클릭 += 발행자1 제공함수

마우스.우클릭 += 발행자2 제공함수

마우스 휠 += 발행자3 제공함수

.

.

.

.

흠... 엄밀히 말하면 모양 자체는 옵저버 패턴과 같은 방식인 것 같다. 


결국 목적은 옵저버 패턴과 같으니까(발행/구독/취소)


일반적이지 않은 옵저버 패턴..?




아래는 MSDN에서 그에 대한 내용이다. (이유는 모르겠지만 List를 가짐)


https://msdn.microsoft.com/ko-kr/library/hy3sefw3(v=vs.80).aspx


namespace BaseClassEvents
{
    using System;
    using System.Collections.Generic;

    // Special EventArgs class to hold info about Shapes.
    public class ShapeEventArgs : EventArgs
    {
        private double newArea;

        public ShapeEventArgs(double d)
        {
            newArea = d;
        }
        public double NewArea
        {
            get { return newArea; }
        }
    }

    // Base class event publisher
    public abstract class Shape
    {
        protected double area;

        public double Area
        {
            get { return area; }
            set { area = value; }
        }
        // The event. Note that by using the generic EventHandler<T> event type
        // we do not need to declare a separate delegate type.
        public event EventHandler<ShapeEventArgs> ShapeChanged;

        public abstract void Draw();

        //The event-invoking method that derived classes can override.
        protected virtual void OnShapeChanged(ShapeEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<ShapeEventArgs> handler = ShapeChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class Circle : Shape
    {
        private double radius;
        public Circle(double d)
        {
            radius = d;
            area = 3.14 * radius;
        }
        public void Update(double d)
        {
            radius = d;
            area = 3.14 * radius;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            // Do any circle-specific processing here.

            // Call the base class event invocation method.
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a circle");
        }
    }

    public class Rectangle : Shape
    {
        private double length;
        private double width;
        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
        }
        public void Update(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            // Do any rectangle-specific processing here.

            // Call the base class event invocation method.
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a rectangle");
        }

    }

    // Represents the surface on which the shapes are drawn
    // Subscribes to shape events so that it knows
    // when to redraw a shape.
    public class ShapeContainer
    {
        List<Shape> _list;

        public ShapeContainer()
        {
            _list = new List<Shape>();
        }

        public void AddShape(Shape s)
        {
            _list.Add(s);
            // Subscribe to the base class event.
            s.ShapeChanged += HandleShapeChanged;
        }

        // ...Other methods to draw, resize, etc.

        private void HandleShapeChanged(object sender, ShapeEventArgs e)
        {
            Shape s = (Shape)sender;

            // Diagnostic message for demonstration purposes.
            Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);

            // Redraw the shape here.
            s.Draw();
        }
    }

    class Test
    {

        static void Main(string[] args)
        {
            //Create the event publishers and subscriber
            Circle c1 = new Circle(54);
            Rectangle r1 = new Rectangle(12, 9);
            ShapeContainer sc = new ShapeContainer();

            // Add the shapes to the container.
            sc.AddShape(c1);
            sc.AddShape(r1);

            // Cause some events to be raised.
            c1.Update(57);
            r1.Update(7, 7);

            // Keep the console window open.
            Console.WriteLine();
            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
        }
    }
}


출력

Received event. Shape area is now 178.98
Drawing a circle
Received event. Shape area is now 49
Drawing a rectangle


반응형