옵저버(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
}
}
옵저버 패턴의 동작 원리
- **발행자(Subject)**는 상태가 변경되면 등록된 모든 옵저버(Observer)에게 알림을 보냅니다.
- 옵저버는 발행자로부터 알림을 받으면 자신의 로직에 맞게 반응합니다.
- 발행자와 옵저버는 느슨하게 결합되어 있으며, 옵저버는 동적으로 등록하거나 해지할 수 있습니다.
옵저버 패턴의 장단점
장점
- 결합도 감소: 발행자와 옵저버가 직접적으로 의존하지 않아 유연한 설계가 가능합니다.
- 확장성 향상: 새로운 옵저버를 쉽게 추가할 수 있습니다.
- 재사용성 증가: 발행자와 옵저버를 독립적으로 재사용할 수 있습니다.
- 동적 변경 가능: 옵저버를 런타임에 동적으로 추가하거나 제거할 수 있습니다.
단점
- 성능 문제: 옵저버가 많아질수록 알림 전송 비용이 커질 수 있습니다.
- 순환 참조 문제: 옵저버와 발행자가 서로 참조하면 무한 루프가 발생할 수 있습니다.
- 예측 어려움: 옵저버가 많을 때, 어떤 옵저버가 어떻게 반응할지 예측하기 어렵습니다.
결론
서버 패턴은 상태가 변경될 때 여러 객체에 자동으로 알림을 보내는 시스템을 설계할 때 유용합니다. 이 패턴을 사용하면 발행자와 구독자 간의 결합도를 낮추고 시스템의 유연성과 확장성을 높일 수 있습니다.
하지만 성능 문제나 순환 참조와 같은 단점이 있을 수 있으므로, 시스템의 규모와 복잡성을 고려하여 적절히 사용하는 것이 중요합니다. GUI 애플리케이션, 게시판 구독 시스템, 로그 시스템 등에서 자주 사용됩니다.