PostCover

디자인 패턴을 쉽게 풀어보자 - 옵저버 패턴편

GoF의 23가지 디자인 패턴 중 하나인 '옵저버 패턴(Observer Pattern)'을 쉽게 풀어보기

옵저버(Observer) 패턴

옵저버(Observer) 패턴은 객체 간의 1:N(One-to-Many) 의존 관계를 정의하여 한 객체의 상태 변화가 있을 때, 여러 객체에게 자동으로 알림을 보내는 디자인 패턴입니다.

이 패턴은 발행-구독(Publish-Subscribe) 패턴으로도 불리며, 상태 변화가 빈번한 시스템에서 주로 사용됩니다.

문제점: 상태 변화 시 여러 객체에게 알림이 필요할 때

예를 들어, GUI 애플리케이션에서 버튼 클릭 이벤트가 발생하면 여러 UI 요소가 해당 이벤트에 반응해야 합니다. 클래스 간에 직접적인 참조로 알림을 보내면 결합도가 증가하고 유연성이 떨어지며 유지보수가 어려워집니다.

class TextBox {

    public void update() {
        System.out.println("TextBox updated");
    }
}

class Button {

    private TextBox textBox;

    public void setTextBox(TextBox textBox) {
        this.textBox = textBox;
    }

    public void click() {
        textBox.update();  // 버튼이 텍스트 박스를 직접 조작
    }
}

public class Main {

    public static void main(String[] args) {
        Button button = new Button();
        TextBox textBox = new TextBox();
        button.setTextBox(textBox);

        button.click();  // TextBox updated
    }
}

문제점

  • 결합도 증가: Button이 TextBox에 의존하고 있습니다.
  • 확장성 부족: 새로운 UI 요소가 추가될 때마다 버튼 코드를 수정해야 합니다.
  • 유연성 저하: 동일한 이벤트에 여러 객체가 반응해야 하는 상황에서 코드가 복잡해집니다.

해결 방법: 옵저버 패턴 적용

옵저버 패턴을 사용하면 **발행자(Subject)**와 **구독자(Observer)**가 느슨하게 결합됩니다. 발행자는 상태가 변경될 때 모든 구독자에게 자동으로 알림을 보냅니다. 구독자는 발행자가 알림을 보낼 때 동적으로 등록하거나 해지할 수 있습니다.

Observer 인터페이스 정의

interface Observer {
    void update(String message);
}

Subject(발행자) 클래스 정의

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

class Subject {

    private List<Observer> observers = new ArrayList<>();

    // 옵저버 등록
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 옵저버 해지
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 모든 옵저버에게 알림 전송
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

Observer 구현체 클래스 구현

class TextBox implements Observer {

    @Override
    public void update(String message) {
        System.out.println("TextBox received: " + message);
    }
}

class Label implements Observer {

    @Override
    public void update(String message) {
        System.out.println("Label received: " + message);
    }
}

클라이언트 코드

public class Main {

    public static void main(String[] args) {
        Subject subject = new Subject();

        // 옵저버 객체 생성 및 등록
        TextBox textBox = new TextBox();
        Label label = new Label();
        subject.attach(textBox);
        subject.attach(label);

        // 상태 변경 및 알림 전송
        subject.notifyObservers("Button clicked"); // TextBox received: Button clicked
                                                   // Label received: Button clicked

        // 옵저버 해지 후 다시 알림 전송
        subject.detach(label);
        subject.notifyObservers("TextBox cleared"); // TextBox received: TextBox cleared
    }
}

옵저버 패턴의 동작 원리

  1. **발행자(Subject)**는 상태가 변경되면 등록된 모든 옵저버(Observer)에게 알림을 보냅니다.
  2. 옵저버는 발행자로부터 알림을 받으면 자신의 로직에 맞게 반응합니다.
  3. 발행자와 옵저버는 느슨하게 결합되어 있으며, 옵저버는 동적으로 등록하거나 해지할 수 있습니다.

옵저버 패턴의 장단점

장점

  • 결합도 감소: 발행자와 옵저버가 직접적으로 의존하지 않아 유연한 설계가 가능합니다.
  • 확장성 향상: 새로운 옵저버를 쉽게 추가할 수 있습니다.
  • 재사용성 증가: 발행자와 옵저버를 독립적으로 재사용할 수 있습니다.
  • 동적 변경 가능: 옵저버를 런타임에 동적으로 추가하거나 제거할 수 있습니다.

단점

  • 성능 문제: 옵저버가 많아질수록 알림 전송 비용이 커질 수 있습니다.
  • 순환 참조 문제: 옵저버와 발행자가 서로 참조하면 무한 루프가 발생할 수 있습니다.
  • 예측 어려움: 옵저버가 많을 때, 어떤 옵저버가 어떻게 반응할지 예측하기 어렵습니다.

결론

서버 패턴은 상태가 변경될 때 여러 객체에 자동으로 알림을 보내는 시스템을 설계할 때 유용합니다. 이 패턴을 사용하면 발행자와 구독자 간의 결합도를 낮추고 시스템의 유연성과 확장성을 높일 수 있습니다.

하지만 성능 문제나 순환 참조와 같은 단점이 있을 수 있으므로, 시스템의 규모와 복잡성을 고려하여 적절히 사용하는 것이 중요합니다. GUI 애플리케이션, 게시판 구독 시스템, 로그 시스템 등에서 자주 사용됩니다.