728x90
728x90
옵저버 패턴(Observer Pattern)
- 주체가 어떤 객체(Subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴
- 주체 : 객체의 상태 변화를 보고 있는 관찰자
- 옵저버 : 전달되는 메서드 등을 기반으로, 객체의 상태 변화에 따라 추가 변화 사항이 생기는 객체들
- 주체와 객체를 따로 두지 않고, 상태가 변경되는 객체를 기반으로 구축하기도 한다.
- 옵저버 패턴을 활용한 서비스로는 트위터(Twitter)가 있다.
- 내가 어떤 사람인 주체를 '팔로우' 했다면 주체가 포스팅을 하면 알림이 '팔로워'에게 가게 된다.
- 또한 옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며, MVC(Model-View-Controller) 패턴에도 사용된다.
- 변경 사항이 생겨 주체라고 볼 수 있는 모델(Model)에서 @update()@ 메서드로 옵저버인 뷰(View)에게 알려주고, 이를 기반으로 컨트롤러(Controller) 등이 작동한다.
자바에서의 옵저버 패턴
- 다음 코드는 @topic@을 기반으로 구현한 옵저버 패턴이다.
- 여기에서 @topic@은 주체이자 객체가 된다.
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!!
*/
- @class Topic implements Subject@를 통해 @Subject interface@를 구현했고, @Observer a = new TopicSubscriber("a", topic);@으로 옵저버를 선언할 때 해당 이름과 어떠한 토픽의 옵저버가 될 것인지를 정하였다.
상속과 구현
- 자바의 상속과 구현의 특징과 차이에 대해 알아보자.
상속(@extends@)
- 자식 클래스가 부모 클래스의 메서드 등을 상속 받아 사용한다.
- 자식 클래스에서 추가 및 확장을 할 수 있다.
- 상속을 통해 재사용성, 중복의 최소화가 이루어진다.
구현(@implements@)
- 부모 인터페이스(@interface@)를 자식 클래스에서 재정의하여 구현하는 것
- 상속과는 달리, 반드시 부모 클래스의 메서드를 재정의하여 구현해야 한다.
상속과 구현의 차이?
- 상속은 일반 클래스, @abstract@ 클래스를 기반으로 구현하며, 구현은 인터페이스(@interface@)를 기반으로 구현한다.
자바스크립트에서의 옵저버 패턴
- 자바스크립트에서의 옵저버 패턴은 프록시 객체를 통해 구현할 수 있다.
프록시(Proxy) 객체
- 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체
- 자바스크립트에서 프록시 객체는 2개의 매개변수를 갖는다.
- @target@ : 프록시할 대상
- @handler@ : 프록시 객체의 @target@ 동작을 가로채서 정의할 동작들이 정해져 있는 함수
프록시 객체 구현 코드
const handler = {
get: function(target, name) {
return name === 'name' ? `${target.a} ${target.b}` : target[name]
}
}
const p = new Proxy({a: 'PROXY', b: 'IS A THING'}, handler)
console.log(p.name) // PROXY IS A THING
- @new Proxy@로 선언한 객체의 @a@와 @b@라는 속성에 특정 문자열을 담아서 @handler@에 "@name@이라는 속성에 접근할 때는 @a@와 @b@라는 것을 합쳐서 문자열을 만들어라."를 구현하였다.
- 이렇게 @p@라는 변수에 @name@이라는 속성을 선언하지 않았는데도 @p.name@으로 @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()@ 함수를 통해 속성에 대한 접근을 가로채서 @한국@이라는 속성이 @지옥@에서 @천국@으로 되는 것을 확인할 수 있었다.
Vue.js 3.0의 옵저버 패턴
- 프론트엔드에서 많이 쓴느 프레임워크인 Vue.js 3.0에서 @ref@나 @reactive@로 정의하면 해당 값이 변경되었을 때 자동으로 DOM에 있는 값이 변경된다.
- 이것은 프록시 객체를 이용하여 구현한 옵저버 패턴을 이용한 것이다.
DOM(Document Object Model)이란 문서 객체 모델을 말하며, 웹 브라우저상의 화면을 이루고 있는 요소들을 지칭한다.
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
}
- 위의 코드는 실제로 Vue.js 3.0의 옵저버 패턴이 담긴 코드이다.
- @proxyMap@이라는 프록시 객체를 사용했고, 객체 내부의 @get()@, @set()@ 메서드를 사용한 것을 확인할 수 있다.
참고 사이트
728x90
728x90
'Computer Science > 개념 정리' 카테고리의 다른 글
[CS 개념] 전략 패턴(Strategy Pattern) (0) | 2023.06.12 |
---|---|
[CS 개념] 팩토리 패턴(Factory Pattern) (1) | 2023.06.11 |
[CS 개념] 싱글톤 패턴(Singletone Pattern) (0) | 2023.05.18 |