본문 바로가기

디자인패턴

[디자인 패턴] 옵져버 패턴(Observer Pattern)

정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 정보가 자동으로 갱신되는 방식으로, 일대다(one-to-many) 의존성을 정의한다.

Observer Pattern UML

 

옵저버 패턴에서 주제자는 옵저버에 대한 정보가 없습니다. 오직 옵저버가 특정 인터페이스(Interface)를 구현한다는 것 만 알고 있습니다. 즉, 옵저버가 무슨 동작을 하는지 모른다는 것입니다. 게다가 옵저버는 언제든지 새로 추가되거나 제거될 수 있으며 새로운 형식의 옵저버를 추가할 때에도 주제에 전혀 영향을 주지 않습니다. 이러한 관계를 느슨한 결합(Loose coupling)이라고 합니다.

어떻게 이런 특징이 있을까요? 위의 UML을 확인해 보겠습니다. Subject interface 등록, 해제, 갱신을 위한 API를 제공합니다. 그리고 이를 상속받는 concrete Subject Class 등록, 해제, 갱신을 구현하고 기타 함수도 구현할 수 있습니다. Observer interface는 Subject에서 갱신할 때 호출되는 update API만 제공합니다. 마지막으로 Observer interface를 상속받는 A, B, C Class에 update를 구현합니다. 이것이 옵저버 패턴의 가장 기본적인 구조입니다.

 

예시1)

철수(Observer)가 코딩 공부를 위해 유튜브에서 프로그래밍 관련 영상을 찾고 있습니다. 그런데 Luckygg(Subject)라는 사람의 채널에 좋은 영상들이 있는 것을 알게 됐습니다. 하나 둘 개제 된 영상들을 보게 되다가 어느새 모든 영상들을 보게 됐습니다. 이제 새로운 영상이 업데이트되는 것을 기다리고 있습니다. 그런데 이 영상이 언제 올라올 줄 알고 마냥 기다릴까요? 이때, 채널 구독(Register)을 하여 업데이트 알림(Notify)을 받게 하는 방법이 있습니다. 유튜브 채널 관리자가 영상을 업데이트하면, 구독하고 있는 모든 사용자들에게 알림을 전달하는 것입니다.

 

 

유튜브 채널 옵저버 패턴 예시

예시2)

요즘에는 종이 신문을 보는 사람이 거의 없을 것입니다. 거의 모든 신문사들이 인터넷으로도 제공을 하고, 무료이며 언제 어디서든 스마트폰으로 뉴스를 볼 수 있기 때문이죠. 하지만 지금처럼 스마트폰이 보급되기 전에는 종이 신문을 어떻게 구독했을까요? 사용자(Observer) 신문사(Subject)에 신문 구독을 신청(Register)하면, 신문사에서는 해당 집에 신문을 배달하도록 주소를 등록합니다. 그리고 다음날 새벽에 신문 배달부가 구독자 목록에 있는 모든 집에 신문을 배달(Notify)하게 되죠. 그런데 한 달 뒤, 한 고객이 신문을 원하지 않아 구독을 해지(Remove)했습니다. 그럼 이 고객은 신문사의 구독자 목록에서 제외되겠죠. 다음날 신문 배달부가 갖고 있는 목록에는 이 고객의 주소가 없기 때문에 배달하지 않게 됩니다.

신문자 옵저버 패턴 예시

예시3)

어떤 연예인이 있는데 연예인의 주변을 둘러쌓는 팬들을 떠올리면 쉬울 것 같다. 연예인들은 팬들이 누군지 하나하나 알 필요는 없지만 노래를 하거나 소리를 치거나 해서 모든 팬들에게 자신의 의사를 전달 할 수 가 있다. 여기서 연예인은 "주체"가 되고 팬들은 "옵저버"가 된다.

  • Talent Interface - 연예인이 되기 위해서는 연예인 인터페이스를 포함 해야 한다.
  • Singer Class - 가수는 연예인 인터페이스를 포함 해야 된다.
  • Fan Interface - 팬이 되기 위해서는 팬 인터페이스를 포함해야 한다.
  • SingerFan Class - 가수를 좋아하는 팬을 구현하는 클래스

 

Subject Interface
public interface Talent {
    public void addFan(Fan fan);
    public void deleteFan(Fan fan);
    public void speak();
}

 

Concrete Subject
public class Singer implements Talent{
    private LinkedList<Fan> fanList;

    public Singer() {
        fanList = new LinkedList<>();
    }

    @Override
    public void addFan(Fan fan) {
        fanList.add(fan);
    }

    @Override
    public void deleteFan(Fan fan) {
        if(fanList.indexOf(fan) != -1) {
            fanList.remove(fan);
        }
    }

    @Override
    public void speak() {
        String [] voices = {"상상더하기", "바라만 본다", "나를 아는 사람", "난 너를 사랑해"};
        Random r =new Random();
        int index = r.nextInt(voices.length);
        for(int i = 0; i < fanList.size(); i++) {
            Fan fan = fanList.get(i);
            fan.hear(voices[index]);
        }
    }
}

 

Observer Interface
public interface Fan {
    public void hear(String voice);
}

 

Concrete Observer
public class SingerFan implements Fan{
    private String name;

    public SingerFan(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void hear(String voice) {
        System.out.println(name + "은 \"" + voice + "\"를 들었습니다!");
    }
}

 

Main 메소드
public class ObserverExample {
    public static void main(String[] args) {
        Singer msg워너비 = new Singer(); // 가수 탄생

        // 팬 생성
        SingerFan person1 = new SingerFan("일반인1");
        SingerFan person2 = new SingerFan("일반인2");

        // 가수를 보고 팬들이 달라붙음
        msg워너비.addFan(person1);
        msg워너비.addFan(person2);

        msg워너비.speak(); // 가수가 노래를 부릅니다.

        // 팬 한면이 가수를 보기 싫어해서 탈되합니다.
        msg워너비.deleteFan(person2);

        // 새로운 팬이 되기로 결심합니다.
        SingerFan person3 = new SingerFan("일반인3");
        msg워너비.addFan(person3);

        msg워너비.speak(); // 가수가 노래를 부릅니다.
    }
}

 

실행결과