1. 옵저버 패턴
옵저버 패턴(Observer Pattern)은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다
메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴
여기서 주체란 객체의 상태 변화를 보고 있는 관찰자이며,
옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미한다.
-> 위의 그림처럼 주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 한다.
1) 트위터
옵저버 패턴을 활용한 서비스
내가 어떤 사람인 주체를 '팔로우'했다면 주체가 포스팅을 올리게 되면 알림이 '팔로워'에게 가야한다.
2) MVC 패턴
주체라고 볼 수 있는 모델(model)에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰에 알려주고
이를 기반으로 컨트롤러(controller) 작동
2. 자바에서의 옵저버 패턴
import java.util.ArrayList;
import java.util.List;
interface Subject {
public void register(Observer obj);
public void unregister(Observer obj);
public void notifyObservers();
public Object getUpdate(Observer obj);
}
interface Observer {
public void update();
}
class Topic implements Subject {
private List<Observer> observers;
private String message;
public Topic() {
this.observers = new ArrayList<>();
this.message = "";
}
@Override
public void register(Observer obj) {
if (!observers.contains(obj)) observers.add(obj);
}
@Override
public void unregister(Observer obj) {
observers.remove(obj);
}
@Override
public void notifyObservers() {
this.observers.forEach(Observer::update);
}
@Override
public Object getUpdate(Observer obj) {
return this.message;
}
public void postMessage(String msg) {
System.out.println("Message sended to Topic: " + msg);
this.message = msg;
notifyObservers();
}
}
class TopicSubscriber implements Observer {
private String name;
private Subject topic;
public TopicSubscriber(String name, Subject topic) {
this.name = name;
this.topic = topic;
}
@Override
public void update() {
String msg = (String) topic.getUpdate(this);
System.out.println(name + ":: got message >> " + msg);
}
}
public class HelloWorld {
public static void main(String[] args) {
Topic topic = new Topic();
Observer a = new TopicSubscriber("a", topic);
Observer b = new TopicSubscriber("b", topic);
Observer c = new TopicSubscriber("c", topic);
topic.register(a);
topic.register(b);
topic.register(c);
topic.postMessage("amumu is op champion!!");
}
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/
topic을 기반으로한 옵저버 패턴
- topic - 주체이자 객체
- class Topic implements Subject를 통해 Suject interface 구현
- Observer a = new TopicSubscriber("a", topic) - 옵저버를 선언할 때 해당 이름과 어떠한 토픽의 옵저버가 될 것인지 정함
3. 자바스크립트에서의 옵저버 패턴
프록시 객체를 통한 구현
프록시 객체
- 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체
- 매개변수
- target - 프록시할 대상
- handler - target 동작을 가로채고 어떠한 동작을 할 것인지가 설정되어 있는 함수
const handler = {
get: function(target, name) {
return name === 'name' ? `${target.a} $(target.b}` : target[name]
}
}
const p = new Proxy({a: "MINHYEOK", b: "IS GOOD"}, handler);
console.log(p.name); // MINHYEOK IS GOOD
- new Proxy()로 a와 b 속성을 가지고 있는 객체와 handler 함수를 매개변수로 넣고 p라는 변수 선언
- 이후 p의 name 속성을 참조하니 a와 b라는 속성밖에 없는 객체가 handler의 "name이라는 속성에 접근할 때 a와 b를 합쳐서 문자열을 만들라"는 로직에 따라 어떤 문자열을 만든다.
- 이렇게 name 속성 등 특정 속성에 접근할 때 그 부분을 가로채서 어떠한 로직을 강제할 수 있는 것이 프록시 객체
function createReactiveObject(target, callback) {
const proxy = new Proxy(target, {
set(obj, prop, value){
if(value !== obj[prop]){
const prev = obj[prop]
obj[prop] = value
callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`)
}
return true
}
})
return proxy
}
const a = {
"형규" : "솔로"
}
const b = createReactiveObject(a, console.log)
b.형규 = "솔로"
b.형규 = "커플"
// 형규가 [솔로] >> [커플] 로 변경되었습니다
- 프록시 객체의 get() 함수는 속성과 함수에 대한 접근을 가로채며, has() 함수는 in 연산자의 사용을 가로챔
- set() 함수는 속성에 대한 접근을 가로챔
- set() 함수를 통해 속성에 대한 접근을 "가로채"서 형규라는 속성이 솔로에서 커플로 되는 것을 감시할 수 있다.