[cs 스터디] 1-1. 디자인 패턴 - 옵저버 패턴

YooJeeun·2024년 12월 26일

cs 스터디

목록 보기
4/65

옵저버 패턴(observer pattern)

옵저버 패턴은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인패턴이다.
옵저버 패턴을 사용하면 주체의 상태 변화에 따라 옵저버들에게 자동으로 알림을 보내고, 옵저버들은 주체의 상태에 따라 추가적인 작업을 수행할 수 있다.

주체(Subject): 객체의 상태 변화를 보고 있는 관찰자
옵저버(Observer): 객체의 상태 변화에 따라 전달되는메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미

자바에서의 옵저버 패턴

import java.util.ArrayList;
import java.util.List;

// Subject
interface Subject {
    void register(Observer obj);      // 옵저버 등록
    void unregister(Observer obj);    // 옵저버 제거
    void notifyObservers();           // 옵저버들에게 상태 변화 통지
    Object getUpdate(Observer obj);   // 옵저버가 상태를 가져오는 메서드
}

// Observer
interface Observer {
    void update();                    // 상태 변화에 대한 반응
}

// Concrete Subject
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() {
        for (Observer observer : observers) {
            observer.update();  // 상태가 변경될 때 옵저버들에게 알림
        }
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;  // 현재 상태 메시지 반환
    }

    public void postMessage(String msg) {
        System.out.println("Message sent to Topic: " + msg);
        this.message = msg;
        notifyObservers();  // 상태가 변경되었을 때 옵저버들에게 통지
    }
}

// Concrete Observer
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!!");
    }
}

Topic(주체) 클래스는 옵저버 리스트와 상태 메시지를 관리한다.
register 메서드는 옵저버를 등록하고 unregister 메서드는 옵저버를 제거한다.
상태 변화가 있을 때 마다 notifyObservers 메서드가 호출되어 모든 등록된 옵저버들에게 상태 변화를 알림
TopicSubscriber 클래스는 주체의 상태가 변경될 때 update 메서드를 호출하여 새로운 상태 메시지를 처리함
postMessage 메서드에서 상태 메시지를 업데이트하고 notifyObservers를 통해 옵저버들에게 상태 변화를 알린다.

결과:

Message sent 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!!

각옵저버가 상태가 변경되었을 때 알림을 받고 주체의 상태 메시지를 출력하는 모습을 확인할 수 있다.

자바의 상속과 구현

상속
상속(extends)은 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며자식 클래스에서 추가 및 확장을 하는 것 -> 재사용성, 중복성 최소화가 이루어진다.
구현
구현(implements)은 부모 인터페이스(interface)를 자식 클래스에서 재정의하여 구현하는 것. 상속과는달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야 한다.
상속과 구현의 차이
상속은 일반 클래스, abstract클래스를 기반으로 구현
구현은 인터페이스를 기반으로 구현

자바스크립트에서의 옵저버 패턴

프록시 객체란

프록시 객체는 JavaScript에서 객체의 기본 동작을 가로채고 새로운 동작을 정의할 수 있는 객체이다.
객체의 속성 접근, 수정, 함수 호출 등을 감시하거나, 재정의하거나, 추가적인 작업을 수행하기 위해 사용된다.
자바 스크립트에서 프록시 객체는 두 개의 매개 변수를 가진다.

  • target: 프록시할 대상(프록시로 감싸고자 하는 원래의 객체)
  • handler: 프록시 객체의 target 동작을 가로채서 정의할 동작들이 정해져 있는 함수
const handler = {
    get: function(target, name) { 
        return name === 'name' ? `${target.a} ${target.b}` : target[name]
    }
}
const p = new Proxy({a: 'KUNDOL', b: 'IS AUMUMU ZANGIN'}, handler)
console.log(p.name) // KUNDOL IS AUMUMU ZANGIN

get 트랩 함수는 객체의 속성에 접근할 때 동작을 가로채는 역할을 한다. 여기서 target은 원래 객체, name은접근하려는 속성의 이름이다.
p.name을 호출하면 name이라는 속성명을 검사 후 조건에 따라 target.atarget.b를 결합한 문자열을 반환한다.
다른 속성(a,b)에 접근할 경우 원래의 값(target[name])을 반환한다.
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.형규 = "커플"
// 형규가 [솔로] >> [커플] 로 변경되었습니다.

a객체에 "형규" 속성이 "솔로" 라는 값을 가짐
createReactiveObject를 호출하여 a를 감시 대상으로 감싸는 프록시 객체 b를 생성. 이때 속성 변경 시 실행할 콜백으로 console.log 전달

b.형규="솔로" 실행
값이 이전과동일 하므로 변경되지 않음. 콜백 호출X
b.형규="커플" 실행
set트랩 함수가 호출되고 값이 변경되었으므로 콜백 호출O
콘솔에 형규가 [솔로] >> [커플]로 변경되었습니다. 출력

프록시 객체를 활용하면 옵저버 패턴을 간단히 구현할 수 있으며 데이터의 변화 감시 및 반응 처리와 같은 상황에서 유용하게 사용된다.

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

DOM(Document Object Model)
문서 객체 모델을 말하며, 웹 브라우저상의 화면을 이루고 있는 요소들을 지칭한다.

profile
제니벨로그

0개의 댓글