[디자인 패턴] 옵저버 패턴 / 프록시 패턴

k·2024년 2월 21일
0

디자인 패턴

목록 보기
4/4

옵저버 패턴

옵저버 패턴이 뭘까?


옵저버 패턴은 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메소드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴이다.

주체란

상태 변화를 지켜보는 관찰자이다.

옵저버란?

이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체들을 의미한다.

옵저버 패턴이 어디서 사용돼?

옵저버 패턴은 위의 설명을 들으면 알겠지만, SNS 서비스에서 주로 사용된다.

인스타나 트위터와 같이 팔로우한 사람이 새로운 정보를 갱신하면, 그 정보를 옵저버들이 바로 알 수 있다.

그리고 옵저버 패턴은 주로 이벤트 기반 시스템에서 사용하기 떄문에 MVC 패턴에도 사용된다.

  • 주체 - model
  • 옵저버 - view

즉, model에 변경이 생겨 메소드를 통해서 옵저버인 "View"에 알려주고 이를 기반으로 "Controller"가 동작한다.

어떻게 구현할 수 있어?

이는 자바와 자바스크립트에서의 구현이 조금 차이가 있기 때문에 두 형식 모두 보여주겠다.

[JAVA CODE]

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 sent 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 Main {
    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에 대해 a,b,c 가 구독하고 있는 상황이다.

  • 해당 topic은 observer를 리스트 형태로 가지고 있다.
  • topic 을 구독하는 a,b,c 옵저버를 만든다.
  • register 메소드를 통해서 a,b,c 유저를 가입 시킨다.
    • topic 에 observer 리스트에 포함된다.
  • 모두가 구독한 topic에서 메세지를 보내게 된다.
    • notifyObservers를 통해서 모든 옵저버에게 메세지를 보내게된다.
          this.observers.forEach(Observer::update);

      라는 부분이 조금 이해가 안될 수도 있어서 가지고 왔다.

      • this.observers 는 Observer 리스트이다.
        • new TopicSubsciber()라는 Observer의 구현체가 내부에 담긴다.(DI) - 주입
      • forEach로 해당 list 요소마다 접근한다.
        • Observer::update는 해당 요소의 update 라는 구현체를 실행하라는 의미이다.

이와 같이 자바에서 옵저버 패턴을 구현할 수 있다.

상속과 구현의 차이

이전에 어디에는 상속, 어디에는 구현을 사용해서 이 둘이 비슷한게 아닐까? 라는 생각을 할 수도 있다.

그래서 명확한 차이를 제시하기 위해서 이를 정리하고자 한다.

  • 상속
    자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있다.
  • 구현
    구현은 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것을 말하며, 상속과는 달리 오버라이드가 필수적이다.

즉, 상속은 일반 클래스고 abstract를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현한다.

[ JAVA SCRIPT CODE ]

먼저 자바스크립트에서 옵저버 패턴을 적용시키기 위해서는 프록시 객체라는 개념을 알고 있어야한다.
프록시 객체없이도 만들 수 있지만, 이를 통해서 옵저버와 같은 동작을 만들어 낼 수 있다.그래서 해당 패턴은 옵저버 패턴이 아닌 프록시 패턴으로 불린다.

프록시 패턴은 그럼 다른거야?

엄밀이 말하면 같은 패턴이라고는 할 수 없다. 비슷하다 정도로 생각하면 좋을거 같다.

프록시 패턴은 주체 접근하기 전에 그 접근에 대한 흐름을 가로채 해당 접근을 필터링 하거나 수정하는 계층적 구조를 가진 디자인 패턴이다.

설명에서도 보이지만, 큰 차이는 없다.

프록시 객체? 그게 뭔데?

프록시 객체는 어떤 대상(target)의 기본적인 동작의 작업을 가로챌 수 있는 객체이다.
아래와 같은 매개변수를 가질 수 있다.

  • target : 프록시할 대상
  • handler : target 동작을 가로채고 어떠한 동작을 할 것인지
const handler = {
  get: function (target, name) {
    //get으로 name을 요청 했냐?
    return name === "name" ? `${target.a} ${target.b}` : target[name];
  },
};

const test = new Proxy({ a: "I", b: "am a student", x: "asd" }, handler);
console.log(test.x); //x는 name이 아니므로 target[name] 원래 하던 행동
console.log(test.name); //name는 name이 맞으므로, a와 b를 합쳐서 return

이 형태가 프록시의 기본이다. 기존 로직을 가로채서 원하는 로직으로 강제 수행하는 것이 프록시 객체이다.

이제 그럼 본격적으로 만들어보자!

const createReactiveObj = (target, callback)=>{
	const proxy = new Proxy(target, {
      set(obj, prop, value){
      	if(value !== obj[prop]){
        	const prev = obj[prop];
            obj[prop] = value;
            callback(`${prop} is changed to [${prev}] from [${value}]  `);
        }
      }
    })
   	return proxy;
}

const a = {
	"kim", "solo"
}
const b = createReactiveObj(a, console.log)
b.kim = "solo";
b.kim = "couple";
// kim is changed to solo from couple

이런식으로 동작된다.

  • obj 는 a를 가지고 있다.
  • prop은 참조하되는 이름을 말한다. 위 코드에서는 kim
  • value 는 내가 넣을 데이터를 말한다. (solo, couple

원래 동작은

obj[prop] = value

와 같이 동작 된다고 생각하면 된다.

  • value 와 obj[prop]이 같으면 변동사항이 없다는 것이고, 만약 변동이 있다면
  • 이전 데이터 저장하고 현재 데이터로 변경해서
  • Callback을 통해 console.log 로 가게된다.

일들 통해 변경되는 작업에 대한 감시를 할 수 있다.

프록시 서버가 프록시 패턴을 이용한거라고!?

프록시 서버

프록시 서버는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크에 간접적으로 접근할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 말한다.

Nginx가 그 대표적인 예이다.

프록시 서버가 중요햬?

중요하냐고 묻는다면, 당연하다.

Node.js 창시자 라이언 달의 말을 인용해보겠다.

You just may be hacked when some yet-unkown butter overflow is discovered. Not that couldn't happend behind nginx, but somehow having a proxy in front makes me happy"

이는 간단히 말해서 Node.js 의 버퍼 오버플로우 취약점을 극복하기 위해서 nginx를 프록시 서버 앞에 두고 Node.js를 뒤에 두는 것이 좋다고 말한 것이다.

이렇게 만 말하면 조금 "엥?"한 반응을 보일 수도 있다.

이 그림을 한번 보면 이해가 될 것이다. NginX라는 것은 없으면 절대 안되는 요소는 아니다.
하지만, 사람들이 계속 많아지는 현재의 시점에서는 없으면 디메리트되는 요소인 것은 확실하다.

Node.js 특성상 단일 쓰레드이기 때문에 서버의 부하를 받을 수 밖에 없다. 이를 여러개의 서버를 두고 NiginX Proxy를 통해서 제일 바쁘지않은 노드 서버와 맵핑시켜준다.

이 것은 기능적 요소이고, 프록시 측면으로 바라보면 NginX는

  • 실제 포트를 숨겨준다.
  • 서버 앞단에서의 로깅을 처리할 수 있다.

등이 있다.

또한 저건 서버에서의 프록시이다. 반대로 클라이언트에서도 프록시를 만들기도 한다.

프론트와 백엔드가 서로 통신할 때 주로 CORS 에러를 마주치는데 이를 극복 할 수도 있다.

orgin

오리진이라는 것은 프로토콜과 호스트이름 포트만을 사용한 조합이다.

http://127.0.0.1:8080/api 에서 origin 은 http://127.0.0.1:8080이 된다.

서버와 클라이언트에서 테스트를 하는데 클라이언트는 3000, 서버는 8080번의 포트를 가지면 오리진내에 포트가 다르기 때문에 CORS 에러가 발생한다. 이 때 클라이언트에서 프록시를 둠으로써 서버에 요청되는 오리진을 변경하여 8080번 포트로 맵핑 될 수 있게 한다.

이와 같이 CORS 문제를 극복 할 수 있다.

profile
You must do the things you think you cannot do

0개의 댓글