옵저버 패턴은 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메소드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴이다.
주체란
상태 변화를 지켜보는 관찰자이다.
옵저버란?
이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체들을 의미한다.
옵저버 패턴은 위의 설명을 들으면 알겠지만, SNS 서비스에서 주로 사용된다.
인스타나 트위터와 같이 팔로우한 사람이 새로운 정보를 갱신하면, 그 정보를 옵저버들이 바로 알 수 있다.
그리고 옵저버 패턴은 주로 이벤트 기반 시스템에서 사용하기 떄문에 MVC 패턴에도 사용된다.
- 주체 - model
- 옵저버 - view
즉, model에 변경이 생겨 메소드를 통해서 옵저버인 "View"에 알려주고 이를 기반으로 "Controller"가 동작한다.
이는 자바와 자바스크립트에서의 구현이 조금 차이가 있기 때문에 두 형식 모두 보여주겠다.
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 가 구독하고 있는 상황이다.
this.observers.forEach(Observer::update);
라는 부분이 조금 이해가 안될 수도 있어서 가지고 왔다.
- this.observers 는 Observer 리스트이다.
- new TopicSubsciber()라는 Observer의 구현체가 내부에 담긴다.(DI) - 주입
- forEach로 해당 list 요소마다 접근한다.
- Observer::update는 해당 요소의 update 라는 구현체를 실행하라는 의미이다.
이와 같이 자바에서 옵저버 패턴을 구현할 수 있다.
상속과 구현의 차이
이전에 어디에는 상속, 어디에는 구현을 사용해서 이 둘이 비슷한게 아닐까? 라는 생각을 할 수도 있다.
그래서 명확한 차이를 제시하기 위해서 이를 정리하고자 한다.
- 상속
자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있다.
- 구현
구현은 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것을 말하며, 상속과는 달리 오버라이드가 필수적이다.즉, 상속은 일반 클래스고 abstract를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현한다.
먼저 자바스크립트에서 옵저버 패턴을 적용시키기 위해서는 프록시 객체라는 개념을 알고 있어야한다.
프록시 객체없이도 만들 수 있지만, 이를 통해서 옵저버와 같은 동작을 만들어 낼 수 있다.그래서 해당 패턴은 옵저버 패턴이 아닌 프록시 패턴으로 불린다.
엄밀이 말하면 같은 패턴이라고는 할 수 없다. 비슷하다 정도로 생각하면 좋을거 같다.
프록시 패턴은 주체 접근하기 전에 그 접근에 대한 흐름을 가로채 해당 접근을 필터링 하거나 수정하는 계층적 구조를 가진 디자인 패턴이다.
설명에서도 보이지만, 큰 차이는 없다.
프록시 객체는 어떤 대상(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[prop] = value
와 같이 동작 된다고 생각하면 된다.
일들 통해 변경되는 작업에 대한 감시를 할 수 있다.
프록시 서버는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크에 간접적으로 접근할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 말한다.
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 문제를 극복 할 수 있다.