옵저버 패턴
한 객체(Subject)의 상태가 바뀌면 그 객체에 의존하는 다른 객체(Observer)에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.
일상생활 속 경험을 생각해보면 옷을 사고 나서 옷이 집에 도착하게 되면 보통 문자나 카톡으로 알림을 주는데 이때 집에 있으면 바로 뛰쳐나가 택배를 받아보는 반응을 하는데 이런 상황이 옵저버 패턴과 비슷하다고 생각합니다.
이 외의 생각나는 상황
아침에 기상을 위한 핸드폰의 알람운동 후 먹는 프로틴디자인 패턴을 공부하고 블로그에 정리하기
옵저버 패턴의 구조
Observer 인터페이스에는 update()라는 함수가 있다 update()는 이벤트가 발생했을 때 처리할 행위를 정의한다.
Subject에는 observerCollection이 존재하는데 여기에 Observer객체들이 저장됩니다.
그리고 notifyObservers()는 Subject에서 상태가 바뀔 때마다 모든 옵저버에게 연락하는 메서드입니다.
registerObserver(observer)는 observer을 등록, unregisterObserver(observer)는 특정 observer를 리스트에서 제거한다.
경험을 다시 떠올려보면 Suject는 배송기사라고 생각할 수 있고 Observer는 배송받는 사람이라고 생각할 수 있습니다.
ObserverA에 집에 택배가 도착하면 상속받은 update()를 통해 구매확정을 할 수 있고
ObserverB에 집에 택배가 도착했지만 맘에 안 들면 교환신청을 할 수 있습니다.
여기서 알 수 있듯 어떤 상황이 일어났을 때 각 클래스에 맞는 메서드를 실행할 수 있습니다.
옵저버 패턴의 장점과 단점
장점
- 객체간 결합도가 느슨해진다.
- 실시간으로 변하는 데이터를 직접 조회하지 않고도 알 수 있다.
단점
- 여러 개의 옵저버 등록 후 해지하지 않을 경우 메모리 누수가 발생할 수 있다.
- 알림 순서를 제어할 수 없다.
옵저버 패턴 사용 예시
이제까지 말한 택배로 예제 코드를 만들어 보겠습니다.
public interface DeliveryDriver {
void sendMsg(Person person);
void blockMsg(Person person);
void notifyPersons(String msg);
}
public class CJDriver implements DeliveryDriver {
private List<Person> personList = new ArrayList<>();
@Override
public void sendMsg(Person person) {
personList.add(person);
}
@Override
public void blockMsg(Person person) {
personList.remove(person);
}
@Override
public void notifyPersons(String msg) {
for (Person person : personList) {
person.update(msg);
}
}
public void deliver(String message) {
notifyPersons(message);
}
}
public interface Person {
void update(String msg);
}
public class ConfirmedPerson implements Person {
@Override
public void update(String msg) {
if (msg.equals("도착했습니다")) {
System.out.println("와! 드디어 왔다");
}
}
public class ReturnPerson implements Person{
@Override
public void update(String msg) {
if (msg.equals("도착했습니다")) {
System.out.println("아 이거 맘에 안들어 교환해야겠다");
}
}
}
public class DeliveryTest {
public static void main(String[] args) {
CJDriver cjDriver = new CJDriver();
ConfirmedPerson confirmedPerson = new ConfirmedPerson();
ReturnPerson returnPerson = new ReturnPerson();
cjDriver.sendMsg(confirmedPerson);
cjDriver.sendMsg(returnPerson);
cjDriver.deliver("도착했습니다");
}
}
이렇게 만들어보긴 했는데 코드를 칠수록 옵저버 패턴은 맞긴 한데 주제가 뭔가 안 어울리고 메서드도 조금 이상하다고 생각이 들어서 유튜브와 구독자로 한 번 다시 만들어 보겠습니다.
저는 유튜브 프리미엄도 안 쓸뿐더러 멤버십 가입조차도 안 해봤는데 어느 날 진용진의 없는 영화(이상한 유튜버 도기도)를 보고서 다음 내용이 너무 궁금해서 멤버십 구독을 한 달간 했었는데 그 기억을 다시 떠올리면서 만들어보겠습니다.
public interface JinYongJinYouTube {
void addSubscriber(Subscriber subscriber);
void removeSubscriber(Subscriber subscriber);
void notifySubscriber(String message);
}
public class JinYongJinMembership implements JinYongJinYouTube {
private List<Subscriber> subscribers = new ArrayList<>();
@Override
public void addSubscriber(Subscriber subscriber) {
subscribers.add(subscriber);
}
@Override
public void removeSubscriber(Subscriber subscriber) {
subscribers.remove(subscriber);
}
@Override
public void notifySubscriber(String title) {
for (Subscriber subscriber : subscribers) {
subscriber.update(title);
}
}
public void sendPreviewVideo(String title) {
System.out.println(title + " 미리보기 영상이 멤버십채널에 업로드되었습니다.");
notifySubscriber(title);
}
}
public interface Subscriber {
void update(String message);
}
public class MembershipSubscriber implements Subscriber {
private String name;
public MembershipSubscriber(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " : 빨리 " + message + " 봐야지!");
}
}
public class YouTubeTest {
public static void main(String[] args) {
JinYongJinMembership membership = new JinYongJinMembership();
MembershipSubscriber 권재투 = new MembershipSubscriber("권재투");
MembershipSubscriber 박재준 = new MembershipSubscriber("박재준");
MembershipSubscriber 김연지 = new MembershipSubscriber("김연지");
MembershipSubscriber 신미영 = new MembershipSubscriber("신미영");
membership.addSubscriber(권재투);
membership.addSubscriber(박재준);
membership.addSubscriber(김연지);
membership.addSubscriber(신미영);
membership.sendPreviewVideo("이상한 유튜버 도기도");
membership.removeSubscriber(권재투); // 도기도가 끝나 구독 해제
System.out.println();
membership.sendPreviewVideo("머니게임");
}
}
이상한 유튜버 도기도 미리보기 영상이 멤버십채널에 업로드되었습니다.
권재투 : 빨리 이상한 유튜버 도기도 봐야지!
박재준 : 빨리 이상한 유튜버 도기도 봐야지!
김연지 : 빨리 이상한 유튜버 도기도 봐야지!
신미영 : 빨리 이상한 유튜버 도기도 봐야지!
머니게임 미리보기 영상이 멤버십채널에 업로드되었습니다.
박재준 : 빨리 머니게임 봐야지!
김연지 : 빨리 머니게임 봐야지!
신미영 : 빨리 머니게임 봐야지!
디자인 원칙
상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다.
느슨하게 결합하는 디자인을 사용하면 변경사항이 생겨도 객체 사이의 상호 의존성을 최소화할 수 있기 때문에 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다.
느슨한 결합
느슨한 결합은 객체들이 상호작용을 할 수는 있지만, 서로 잘 모르는 관계를 말합니다.
옵저버 패턴은 느슨한 결합의 훌륭한 예시입니다.
코드에서의 느슨한 결합은 이렇게 설명할 수 있습니다.
인터페이스 사용 : JinYongJinYouTube와 Subscriber 인터페이스를 통해 기능을 정의하고, 구체적인 클래스가 이 인터페이스를 구현하면서 구체적인 기능을 제공하도록 하는 추상화를 제공합니다.
객체 간 의존성 : JinYongJinMembership 클래스는 Subscriber 인터페이스에 의존하고 있습니다. 구독자들은 Subscriber 인터페이스를 구현함으로써 언제든지 멤버십 채널의 알림을 받을 수 있습니다.
느슨한 결합을 통한 유연성 : 구독자들은 update 메서드를 구현함으로써 자신의 동작을 정의할 수 있고, 이는 멤버십 채널이 새로운 기능을 도입하거나 변경할 때, 구독자 객체들에게 영향을 주지 않고 변경할 수 있습니다.
객체의 생성 및 관리: JinYongJinMembership 클래스는 구독자 객체를 직접 생성하지 않고, Subscriber 인터페이스에 의존하여 객체를 다루고 있습니다.
이는 유연성을 높이고, 새로운 종류의 구독자를 손쉽게 추가하거나 제거할 수 있도록 합니다.
'개발 도서' 카테고리의 다른 글
디자인 패턴 - 커맨드 패턴 (0) | 2023.12.09 |
---|---|
디자인 패턴 - 싱글톤 패턴 (0) | 2023.12.04 |
디자인 패턴 - 팩토리 메서드 패턴, 추상 팩토리 패턴 (1) | 2023.12.02 |
디자인 패턴 - 데코레이터 패턴 (0) | 2023.11.28 |
헤드 퍼스트 디자인 패턴 - 1장 전략패턴 (1) | 2023.11.20 |