Observer pattern

Minji·2022년 11월 27일
0

기술면접

목록 보기
5/11

Observer pattern

  • 옵저버 패턴은 디자인 패턴 중 하나
  • 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려준다.
  • 이 주체와 옵저버 사이의 연결은 인터페이스를 이용해서 느슨한 결합을 유지하며, 주체, 옵저버 인터페이스를 사용한다.
❓ 주체 : 객체의 상태 변화를 보고 있는 관찰자
❓ 옵저버 : 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미.

객체와 주체가 분리되어 있을 수도 있고, 합쳐져 있을 수도 있고 주체와 객체를 구분짓지 않을 수도 있다.

[객체 주체 분리, 합체]

image

[주체,객체 구분 x]

예시 : 트위터
주체를 팔로우 한 사람들이 옵저버, 주체가 새로운 트윗을 올리면 옵저버들에게 알려줌.

옵저버 패턴 구조

  • 이벤트 기반 시스템에 사용
  • MVC패턴에도 사용된다.

image
모델에서 변경사항이 생기면 view에 update()메서드를 통해 알려주고, controller는 이를 기반으로 동작한다.

예시

코드 출처

Java

JAVA코드
/*
코드 출처 : 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은 클래스로 구현되어 있기 때문에 상속을 해야하고, 자바는 다중 상속이 안되기 때문에 이로 인한 한계가 있다.

JavaScript

프록시 객체를 통해 구현

❓ 프록시 객체 : 어떤 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체
자바스크립트에서 프록시 객체는 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

Vue.js 3.0에서 refreactive로 정의하면 해당 값이 변경되었을 때 자동으로 DOM에 있는 값이 변경된다. 이는 프록시 객체를 이용한 옵저버 패턴을 이용하여 구현한 것

Vue.js 3.0

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
}

참고

profile
매일매일 성장하기 : )

0개의 댓글