🚀 옵저버 패턴이란?
- 주제(subject)가 변경될 때마다 옵저버(observer)들에게 변화를 알려주는 디자인 패턴
- 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의함
주제와 객체를 따로 두는 경우도 있음. 즉, 주제가 객체를 관찰하다가 변화가 생기면 옵저버들에게 알려주는 것
주제는 옵저버들이 인터페이스를 구현한다는 것을 제외하면 옵저버에 대해 아무것도 모른다. 따라서 이들 사이의 결합은 느슨한 결합(Loose Coupling)이다.
- 옵저버는 언제든지 새로 추가 및 제거 가능
- 새로운 형식의 옵저버를 추가할 때도 주제는 변경할 필요 X
- 주제와 옵저버는 서로 독립적으로 재사용 O
- 주제나 옵저버가 달라져도 서로에게 영향 X
🚀 예시 코드 작성해보기
Producer(프로듀서)의 데이터가 변경될 때마다 Consumer(컨슈머)들에게 변화를 알려주는 예시를 작성해보자.
- 주제는 여러개의 옵저버가 있을 수 있음
- 주제는 상태가 바뀔때마다 notifyObservers()로 옵저버들에게 알려줘야 함
- 옵저버가 되려면 Observer 인터페이스만 구현하면 됨
Subject 인터페이스
옵저버 등록, 제거, 변화를 알려주는 메서드가 있음
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
Producer 클래스
- Subject 인터페이스를 구현한 클래스
- 상태값으로 dataA, dataB를 가짐
- 전체 상태값을 변경하는 setData() 사용 시, notifyObservers()로 옵저버들에게 변화를 알려줌
- 상태값 각각의 getter 메서드도 구현되어 있음. 이는 옵저버들이 풀 방식으로 상태값을 직접 가져오기 위함
옵저버 패턴을 사용하면 주제가 데이터를 보내는 푸쉬(push) 방식과 옵저버들 각각 원하는 데이터만 직접 가져오는 풀(pull) 방식이 있다. 일반적으로 풀 방식이 더 옳은 방식으로 간주하고 있다.
public class Producer implements Subject {
private final List<Observer> observers;
private String dataA;
private String dataB;
public Producer() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
observers.forEach(Observer::update);
}
public void setData(String dataA, String dataB) {
this.dataA = dataA;
this.dataB = dataB;
notifyObservers();
}
public String getDataA() {
return dataA;
}
public String getDataB() {
return dataB;
}
}
Observer 인터페이스
public interface Observer {
void update();
}
ConsumerA 클래스
- Observer 인터페이스를 구현한 클래스
- 주제를 저장하지 않아도 되지만, 추후 옵저버 목록에서 탈퇴할 때 유용함
public class ConsumerA implements Observer {
private String dataA;
private Producer producer;
public ConsumerA(Producer producer) {
this.producer = producer;
producer.registerObserver(this);
}
@Override
public void update() {
dataA = producer.getDataA();
System.out.println("ConsumerA가 수정됨 : " + dataA);
}
}
ConsumerB 클래스
- ConsumerA와는 다르게 dataB만을 필요로하는 클래스
- 따라서 주제가 변경되면 주제에서 원하는 데이터만 직접 가져오는 풀 방식이 더 좋음
public class ConsumerB implements Observer {
private String dataB;
private Producer producer;
public ConsumerB(Producer producer) {
this.producer = producer;
producer.registerObserver(this);
}
@Override
public void update() {
dataB = producer.getDataB();
System.out.println("ConsumerB가 수정됨 : " + dataB);
}
}
테스트해보기
class ObserverTest {
@Test
void start() {
Producer producer = new Producer();
ConsumerA consumerA = new ConsumerA(producer);
ConsumerB consumerB = new ConsumerB(producer);
producer.setData("AAA", "BBB");
producer.removeObserver(consumerA); // consumerA 옵저버 제거
producer.setData("CCC", "DDD");
}
}
/*
ConsumerA가 수정됨 : AAA
ConsumerB가 수정됨 : BBB
ConsumerB가 수정됨 : DDD
*/
위 코드는 여기를 클릭하면 나옴
🚀 결론
- 주제와 옵저버 모두 인터페이스를 사용해서 느슨한 결합을 만듦
- 옵저버 패턴에서는 구성을 활용해서 옵저버들을 관리하므로 확장성 좋음
- 옵저버들은 서로 독립적이기 때문에 하나의 옵저버가 문제 발생하더라도 다른 옵저버는 영향이 없음
- 옵저버 객체가 많아질수록 모든 옵저버 객체에 대한 처리를 해야하므로 성능 저하될 가능성 있음
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
Decorator(데코레이터) 패턴이란? (0) | 2023.04.18 |
---|---|
Strategy Pattern(전략 패턴)이란? (0) | 2023.03.21 |
Delegate Pattern(델리게이트 패턴, 위임 패턴)이란? (1) | 2023.03.15 |