ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Design Pattern] 옵저버 패턴 (Observer Pattern)
    공부/디자인 패턴 2021. 5. 22. 22:44

    옵저버 패턴이란 한 객체의 상태가 변경이 되면 해당 객체를 의존하고 있는 모든 객체에게 의존하고 있는 객체의 상태가 변경되었다고 알려주는 디자인 패턴입니다. 여기서 상태가 변경되었는지 관찰 대상 객체를 subject라 하고 이러한 변경을 관찰하는 객체를 observer라 합니다. 즉, observer 객체들은 subject에 의존성을 갖습니다. subject 객체를 관찰하여 변경을 감지하고자 하는 객체를 observer로 등록하고 변경이 되었을 때, 이를 탐지할 수 있습니다.

    이해하기 쉬운 예제를 들면, 유튜브를 생각하면 됩니다.

    a라는 유튜브 채널(subject)을 A, B, C 라는 사람이 구독을 하게 되면(observer) 해당 채널에 새로운 영상이나 글이 올라올 경우(객체의 상태 변경), 해당 유튜브를 구독한 A, B, C는 알림을 받게 됩니다.

    위 예제처럼 옵저버 패턴은 1(subject) : N(observer) 구조를 갖습니다.

    다이어그램

    예제

    먼저 위 유튜브 채널 예시를 옵저버 패턴을 적용하지 않고 코드를 짜게 된다면 다음과 같습니다.

    강한 결합 (Tight Coupling)

    아래는 youtube 객체에 새로운 영상이 올라왔을 때, 구독자 A, B, C 에 업데이트를 하는 로직입니다.

    public class Video {
        private String title;
        private String content;
    }
    
    public class Youtube {
        private Video video;
        private SubscriberA subscriberA;
        private SubscriberB subscriberB;
        private SubscriberC subscriberC;
    
        public Youtube(SubscriberA subscriberA, SubscriberB subscriberB, SubscriberC subscriberC) {
            this.subscriberA = subscriberA;
            this.subscriberB = subscriberB;
            this.subscriberC = subscriberC;
        }
    
        public void newVideo(Video video) {
            this.video = video;
            subscriberA.update(video);
            subscriberB.update(video);
            subscriberC.update(video);
        }
    }
    
    public class SubscriberA {
        public void update(Video video) {
            System.out.println("SubscriberA 영상 알림 ");
        }
    }
    
    public class SubscriberB {
        public void update(Video video) {
            System.out.println("SubscriberB 영상 알림 ");
        }
    }
    
    public class SubscriberC {
        public void update(Video video) {
            System.out.println("SubscriberC 영상 알림 ");
        }
    }

    해당 로직의 문제는 새 영상을 알리는 로직이 구독자들의 update 메소드(실제 구현체)를 알고 있어야 합니다. 이는 구독자 객체나 유튜브 객체의 변경이 서로 영향을 줄 수 있습니다. 또한 유튜브 객체에 구독자가 늘거나 줄어들면 해당 객체를 수정해야 합니다.

    느슨한 결합 (Loose coupling)

    이러한 문제는 인터페이스를 사용해 유튜브와 구독자 객체 간의 의존 관계를 느슨한 결합으로 만들면 해결됩니다. 느슨한 결합이란 두 객체가 인터페이스를 통해 통신하여 서로에 대해 잘 모르게 만드는 것입니다.

    public interface Observer {
        void update(Video video);
    }
    
    public class Youtube {
        private Video video;
        private Observer observer;
    
        public Youtube(Observer observer) {
            this.observer = observer;
        }
    
        public void newVideo(Video video) {
            this.video = video;
            observer.update(video);
        }
    }
    
    public class SubscriberA implements Observer{
        public void update(Video video) {
            System.out.println("SubscriberA 영상 알림 ");
        }
    }
    
    public class SubscriberB implements Observer {
        public void update(Video video) {
            System.out.println("SubscriberB 영상 알림 ");
        }
    }
    
    public class SubscriberC implements Observer {
        public void update(Video video) {
            System.out.println("SubscriberC 영상 알림 ");
        }
    }

    이러면 강한 결합의 문제는 해결되지만 한 객체만 참조가 가능하기 때문에 여러명에게 알림을 줄 수가 없습니다. 이러한 문제를 옵저버 패턴을 사용해 해결할 수 있습니다.

    옵저버 패턴

    푸시(push) 방식

    subject 객체에서 상태 변경을 observer 객체들에게 알릴 때, 변경된 데이터도 함께 push 하는 방식입니다. 푸시 방식은 subject 객체에서 observer 객체들에게 데이터를 직접 전달하기 때문에 observer 객체에서 특별한 구현이 필요 없습니다.

    public interface Observer {
        void update(Video video);
    }
    
    public class SubscriberA implements Observer{
        public void update(Video video) {
            System.out.println("SubscriberA 영상 알림 ");
        }
    }
    
    public class SubscriberB implements Observer {
        public void update(Video video) {
            System.out.println("SubscriberB 영상 알림 ");
        }
    }
    
    public class SubscriberC implements Observer {
        public void update(Video video) {
            System.out.println("SubscriberC 영상 알림 ");
        }
    }
    
    public interface Subject {
        void attach(Observer observer);
        void detach(Observer observer);
        void notifyObserver();
    }
    
    public class Youtube implements Subject {
        private List<Observer> observers;
        private Video video;
    
        public Youtube(List<Observer> observers) {
            this.observers = observers;
        }
    
        @Override
        public void attach(Observer observer) {
            observers.add(observer);
        }
    
        @Override
        public void detach(Observer observer) {
            observers.remove(observer);
        }
    
        @Override
        public void notifyObserver() {
            for (Observer observer: observers) {
                observer.update(video);
            }
        }
    
        public void newVideo(Video video) {
            this.video = video;
            notifyObserver();
        }
    }

    풀(pull) 방식

    subject 객체에게 변경 알림을 받고 observer 객체들이 변경된 데이터를 subject 객체에서 pull 받는 방식입니다. 해당 방식은 각 observer 객체들 마다 관찰하는 데이터가 다르거나 많을 때 사용합니다.

    public interface Observer {
        void update(Subject subject);
    }
    
    public class SubscriberA implements Observer{
        public void update(Subject subject) {
            System.out.println("SubscriberA 영상 알림 ");
            if (subject instanceof Youtube) {
                Youtube youtube = (Youtube) subject;
                Video video = youtube.getVideo();
            }
        }
    }
    
    // 이하 B, C 동일
    
    public class Youtube implements Subject {
        private List<Observer> observers;
        private Video video;
    
        public Youtube(List<Observer> observers) {
            this.observers = observers;
        }
    
        @Override
        public void attach(Observer observer) {
            observers.add(observer);
        }
    
        @Override
        public void detach(Observer observer) {
            observers.remove(observer);
        }
    
        @Override
        public void notifyObserver() {
            for (Observer observer: observers) {
                observer.update(this);
            }
        }
    
        public void newVideo(Video video) {
            this.video = video;
            notifyObserver();
        }
    
        public Video getVideo() {
            return video;
        }
    }

    자바에서는 이러한 패턴이 이미 Observable과 Observer 클래스를 제공하고 있어서 굳이 직접 구현을 하지 않아도 됩니다.

    파이썬

    파이썬에서는 인터페이스가 없으므로 ABCMeta 클래스를 사용해 추상 클래스를 인터페이스 처럼 사용해야 합니다. 그렇지 않으면 강한 결합의 문제를 가질 수 밖에 없습니다.

    푸시 방식

    from abc import ABCMeta, abstractmethod
    from typing import List
    
    class Video:
        pass
    
    class Observer(metaclass=ABCMeta):
        @abstractmethod
        def update(self, video: Video):
            pass
    
    class SubscriberA(Observer):
        def update(self, video: Video):
            print('SubscriberA 영상 알림')
    
    class SubscriberB(Observer):
        def update(self, video: Video):
            print('SubscriberA 영상 알림')
    
    class SubscriberC(Observer):
        def update(self, video: Video):
            print('SubscriberA 영상 알림')
    
    class Subject(metaclass=ABCMeta):
        @abstractmethod
        def attach(self, observer: Observer):
            pass
    
        @abstractmethod
        def detach(self, observer: Observer):
            pass
    
        @abstractmethod
        def notify(self):
            pass
    
    class Youtube(Subject):
        def __init__(self, observers: List[Observer]):
            self.observers = observers
            self.video = None
    
        def attach(self, observer: Observer):
            self.observers.append(observer)
    
        def detach(self, observer: Observer):
            self.observers.remove(observer)
    
        def notify(self):
            for observer in self.observers:
                observer.update(self.video)
    
        def new_video(self, video: Video):
            self.video = video
            self.notify()

    풀 방식

    from abc import ABCMeta, abstractmethod
    from typing import List
    
    class Video:
        pass
    
    class Observer(metaclass=ABCMeta):
        @abstractmethod
        def update(self, subject: Subject):
            pass
    
    class SubscriberA(Observer):
        def update(self, subject: Subject):
            print('SubscriberA 영상 알림')
            if isinstance(subject, Youtube):
                print(subject.video)
    
    # 이하 B, C 동일
    
    class Subject(metaclass=ABCMeta):
        @abstractmethod
        def attach(self, observer: Observer):
            pass
    
        @abstractmethod
        def detach(self, observer: Observer):
            pass
    
        @abstractmethod
        def notify(self):
            pass
    
    class Youtube(Subject):
        def __init__(self, observers: List[Observer]):
            self.observers = observers
            self._video = None
    
        def attach(self, observer: Observer):
            self.observers.append(observer)
    
        def detach(self, observer: Observer):
            self.observers.remove(observer)
    
        def notify(self):
            for observer in self.observers:
                observer.update(self)
    
        def new_video(self, video: Video):
            self._video = video
            self.notify()
    
        @property
        def video(self):
            return self._video

    댓글