❓ 주체 : 객체의 상태 변화를 보고 있는 관찰자
❓ 옵저버 : 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미.
객체와 주체가 분리되어 있을 수도 있고, 합쳐져 있을 수도 있고 주체와 객체를 구분짓지 않을 수도 있다.
예시 : 트위터
주체를 팔로우 한 사람들이 옵저버, 주체가 새로운 트윗을 올리면 옵저버들에게 알려줌.
모델에서 변경사항이 생기면 view에 update()메서드를 통해 알려주고, controller는 이를 기반으로 동작한다.
/*
코드 출처 : https://github.com/gilbutITbook/080326
*/
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
//옵저버에게 update했다고 알리기
public void notifyObservers() {
this.observers.forEach(Observer::update);
}
@Override
//update내용 가져오기
public Object getUpdate(Observer obj) {
return this.message;
}
// 메시지 update(변화를 관찰하고 있는 부분)하고 옵저버에게 알리기
public void postMessage(String msg) {
System.out.println("Message sended to Topic: " + msg);
this.message = msg;
notifyObservers();
}
}
//옵저버(topic 구독)
class TopicSubscriber implements Observer {
private String name;
private Subject topic;
public TopicSubscriber(String name, Subject topic) {
this.name = name;
this.topic = topic;
}
@Override
//옵저버 update()메서드 -> 주체,객체의 notifyObservers()를 통해 작동됨
public void update() {
//update된 msg받아서 출력
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의 옵저버로 등록
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!!
*/
Java에는 옵저버 패턴을 적용할 수 있는 Observer
인터페이스와 Observable
클래스를 기본적으로 제공해준다.
Observable은 클래스로 구현되어 있기 때문에 상속을 해야하고, 자바는 다중 상속이 안되기 때문에 이로 인한 한계가 있다.
프록시 객체를 통해 구현
❓ 프록시 객체 : 어떤 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체
자바스크립트에서 프록시 객체는 target,handler 두개의 매개변수를 가집니다.
target : 프록시할 대상
handler : 프록시 객체의 target동작을 가로채서 실행될 함수
JavaScript코드
/*
target : 대상 객체
property: 설정할 속성의 이름
value: 설정할 속성의 새 값
reveiver vmfhrtl wkcp
set()메서드는 boolean 값을 반환함
*/
new Proxy(target, {
set(target, property, value, receiver) {},
});
/*
코드 출처 : https://github.com/gilbutITbook/080326
*/
function createReactiveObject(target, callback) {
const proxy = new Proxy(target, {
set(obj, prop, value) {
//obj는 a, prop은 형규, value는 커플
if (value !== obj[prop]) {
const prev = obj[prop]; //prev는 솔로
obj[prop] = value;
callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`);
}
return true;
},
});
return proxy;
}
const a = {
형규: "솔로",
};
const b = createReactiveObject(a, console.log);
//b.형규 = "솔로";
b.형규 = "커플";
// 형규가 [솔로] >> [커플] 로 변경되었습니다
Vue.js 3.0에서 ref
나 reactive
로 정의하면 해당 값이 변경되었을 때 자동으로 DOM에 있는 값이 변경된다. 이는 프록시 객체를 이용한 옵저버 패턴을 이용하여 구현한 것
Vue.js의 옵저버 패턴이 담긴 코드
proxyMap이라는 프록시 객치를 사용
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler(any),
collectionHandlers: ProxyHandler(any),
proxyMap: WeakMap<Target, any)
){
if (!isObject(target)) {
if (DEV) {
console.warn("value cannot be made reactive: ${String(target)!")
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target [ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
){
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap. get (target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed
const targetType = getTargetType (target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy (
target,
targetType=== TargetType.COLLECTION ? collectionHandlers:
baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}