본문 바로가기
설계/디자인패턴

[디자인패턴] 옵저버(Observer) 패턴 이해하기

by J4J 2024. 4. 30.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 옵저버 (Observer) 패턴에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Observer 패턴이란?

 

observer 패턴은 객체의 상태 변화를 관찰하는 observer들이 존재하고, observer 들에게 대상 객체의 상태 변화를 통지하는 subject가 존재하는 디자인 패턴을 의미합니다.

 

observer 패턴을 가장 쉽게 이해하기 위해 비교해볼 수 있는 것은 구독입니다.

 

구독이라고 하는 것은 일정한 주기를 가지고 상품을 구매하는 것을 의미하는데, 이를 observer 패턴에 비유를 해보자면 observer들이 subject라는 상품을 구독하고 있다고 표현해 볼 수 있습니다.

 

구독이 되어 있기 때문에 subject에 특정 상태 변화가 발생된다면 관련 정보들을 observer들에게 공유가 되며 observer는 공유된 내용을 기반으로 원하는 행위들을 수행할 수 있습니다.

 

 

반응형

 

 

위의 비교 예시로 얘기해 본 것처럼 observer 패턴은 구독 모델에 대한 메시지 전달에 적용해 볼 수 있습니다.

 

또한 이벤트 처리가 필요한 비즈니스 로직 등에서도 충분히 활용해 볼 수 있습니다.

 

 

 

observer 패턴의 구조는 다음과 같습니다.

 

observer 패턴 구조

 

 

 

 

위에서도 간단히 언급을 했지만 먼저 용어에 대해 설명하면 다음과 같이 적어볼 수 있습니다.

 

  • Subject → Observer에 의해 관찰되는 대상, 상태의 변화가 발생되면 Observer 들에게 전파
  • Observer → Subject를 관찰하는 대상, Subject의 변화에 반응

 

 

 

관찰되는 대상이 필요한 경우에는 Subject에 추상화되어 있는 정보를 상속받아 subject 별 기능을 구현해 볼 수 있습니다.

 

그리고 여기서 중요한 점은 각 subject 들은 observer의 정보를 담아두는 필드가 하나씩 존재한다는 것입니다.

 

subject를 관찰하고 있는 대상 정보를 각각 보관하고 있기 때문에 subject에 상태 변화가 발생되면 관련 정보들을 전달해줘야 하는 observer 들을 쉽게 확인해 볼 수 있습니다.

 

또한 모든 observer 들도 Observer에 추상화되어 있는 정보를 상속받아 기능 구현이 이루어지기 때문에 상태 변화에 대한 감지가 발생했을 때 각 observer 들만이 가지고 있는 기능들도 실행되도록 로직을 구현해 볼 수 있습니다.

 

 

 

 

Observer 패턴 특징

 

observer 패턴의 특징은 다음과 같습니다.

 

 

 

[ 장점 ]

 

  • subject와 observer는 서로 어떤 동작을 수행하는지에 대해 모르기 때문에 loose coupling 함
  • 새로운 subject 또는 observer가 필요할 때 유연하고 확장성 있는 개발 수행 가능
  • 기능 추가 할 때 기존 구현되어 있는 것들을 고려하지 않아도 되며 예상치 못한 side effect 가 발생하지 않음

 

 

 

[ 단점 ]

 

  • subject를 관찰하고 있는 모든 observer 들에 상태 변화를 전파하기 때문에 성능 이슈가 발생할 수 있음
  • 참조가 필요 없는 observer에 대한 처리를 올바르게 수행하지 않을 경우 메모리 누수 발생 가능성이 있음
  • observer의 실행 순서를 보장할 수 없음
 

 

 

특징들을 살펴보면 observer 패턴도 solid 원칙이 준수되고 있는 것을 볼 수 있습니다.

 

각 Subject / Observer에 의해 기능들이 정의되어 구현되기 때문에 ocp의 특징을 살펴볼 수 있습니다.

 

또한 Subject / Observer는 각각의 기능에 대해서만 역할을 수행하기 때문에 srp의 특징도 살펴볼 수 있습니다.

 

그 외에도 oop 관점에서 살펴보면 상속, 추상화 등도 모두 활용되고 있습니다.

 

 

 

 

Observer 패턴 예시 (1) - Before

 

이번엔 observer 패턴 예시에 대해 간단히 작성해 보겠습니다.

 

간단하게 youtube / twitch와 같은 구독 기능을 제공하는 플랫폼과 구독자들이 있는 경우에 대해 구현해 보겠습니다.

 

먼저 observer 패턴을 적용하지 않을 경우는 다음과 같습니다.

 

// observer
package com.jforj.observer.before;

public interface SubscriberObserver {

    void print();

    String getId();
}


// youtube observer
package com.jforj.observer.before;

public class YoutubeObserver implements SubscriberObserver {

    @Override
    public void print() {
        System.out.println("hello. i am " + getId() + " of youtube");
    }

    @Override
    public String getId() {
        return "jforj.youtube";
    }
}


// twitch observer
package com.jforj.observer.before;

public class TwitchObserver implements SubscriberObserver {

    @Override
    public void print() {
        System.out.println("hello. i am " + getId() + " of twitch");
    }

    @Override
    public String getId() {
        return "jforj.twitch";
    }
}


// main
package com.jforj.observer.before;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<SubscriberObserver> subscriberObservers = new ArrayList<>();
        YoutubeObserver youtubeObserver = new YoutubeObserver();
        TwitchObserver twitchObserver = new TwitchObserver();

        // 구독
        System.out.println("subscribe to " + youtubeObserver.getId());
        subscriberObservers.add(youtubeObserver);

        System.out.println("subscribe to " + twitchObserver.getId());
        subscriberObservers.add(twitchObserver);

        // 출력
        subscriberObservers.forEach(subscriberObserver -> {
            subscriberObserver.print();
        });

        // 구독 취소
        System.out.println("subscribe cancle to " + youtubeObserver.getId());
        subscriberObservers.remove(youtubeObserver);

        // 출력
        subscriberObservers.forEach(subscriberObserver -> {
            subscriberObserver.print();
        });
    }
}

 

 

 

코드를 살펴보시면 observer는 존재하지만 subject가 존재하지 않을 경우 구독과 관련된 처리를 수행할 때 client에서 매번 위와 같이 관련 로직을 구성해줘야 합니다.

 

매번 모든 상태 변화에 대해 동일한 내용들을 observer가 전달받아야 할 때 observer 패턴을 적용하지 않을 경우 반복적인 코드가 작성되는 것을 확인할 수 있습니다.

 

 

 

 

Observer 패턴 예시 (2) - After

 

이번엔 위의 코드에서 observer 패턴을 적용해 보겠습니다.

 

// observer
package com.jforj.observer.after;

public interface SubscriberObserver {

    void print();

    String getId();
}


// youtube observer
package com.jforj.observer.after;

public class YoutubeObserver implements SubscriberObserver {

    @Override
    public void print() {
        System.out.println("hello. i am " + getId() + " of youtube");
    }

    @Override
    public String getId() {
        return "jforj.youtube";
    }
}


// twitch observer
package com.jforj.observer.after;

public class TwitchObserver implements SubscriberObserver {

    @Override
    public void print() {
        System.out.println("hello. i am " + getId() + " of twitch");
    }

    @Override
    public String getId() {
        return "jforj.twitch";
    }
}


// subject
package com.jforj.observer.after;

public interface BroadCastingSubject {

    void addObserver(SubscriberObserver subscriberObserver);

    void deleteObserver(SubscriberObserver subscriberObserver);

    void printObserver();
}


// youtube subject
package com.jforj.observer.after;

import java.util.ArrayList;
import java.util.List;

public class YoutubeSubject implements BroadCastingSubject {
    List<SubscriberObserver> subscriberObservers = new ArrayList<>();

    @Override
    public void addObserver(SubscriberObserver subscriberObserver) {
        System.out.println("subscribe to " + subscriberObserver.getId());
        subscriberObservers.add(subscriberObserver);
    }

    @Override
    public void deleteObserver(SubscriberObserver subscriberObserver) {
        System.out.println("subscribe cancle to " + subscriberObserver.getId());
        subscriberObservers.remove(subscriberObserver);
    }

    @Override
    public void printObserver() {
        subscriberObservers.forEach(subscriberObserver -> {
            subscriberObserver.print();
        });
    }
}


// main
package com.jforj.observer.after;

public class Main {
    public static void main(String[] args) {
        YoutubeSubject youtubeSubject = new YoutubeSubject();
        YoutubeObserver youtubeObserver = new YoutubeObserver();

        // 구독
        youtubeSubject.addObserver(youtubeObserver);
        youtubeSubject.addObserver(new TwitchObserver());

        // 출력
        youtubeSubject.printObserver();

        // 구독 취소
        youtubeSubject.deleteObserver(youtubeObserver);

        // 출력
        youtubeSubject.printObserver();
    }
}

 

 

 

만약 youtube 플랫폼의 subject가 존재한다고 가정한다면 youtube에서 처리하고자 하는 기능에 대해 subject를 상속받아 정의해 볼 수 있습니다.

 

디자인 패턴을 적용하기 전과 달리 동일한 상태 변화에 대한 비즈니스 로직 처리를 subject에서 모두 담당하여 수행하고 있기 때문에 client 입장에서는 subject에 정의되어 있는 기능만 호출해 주면 됩니다.

 

그러면 subject를 바라보고 있는 모든 observer 들에게 관련 내용들이 전달되기 때문에 반복적인 로직을 구성해 줄 필요가 없어지게 됩니다.

 

 

 

 

 

 

 

 

이상으로 옵저버 (Observer) 패턴에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글