[백엔드 로드맵 - 개발방법론] Design Pattern Part2

Sierra·2022년 7월 31일
0

Backend-Roadmap

목록 보기
19/43
post-thumbnail

Intro

지난 포스팅에서는 크게 Singleton, Factory, Strategy Pattern 에 대해 다뤘다.

디자인 패턴 포스팅은 예고했듯이 3가지 파트로 나뉜다. 그 만큼 내용도 많고 중요한 개념들이고 정말 많이 와닫는 내용들이라 포스팅에 정말 많은 신경을 쓰고 있는 파트이기도 하다.

이번 포스팅에서는 Observer, Proxy, Iterator Revealing Module에 대해 다뤄 보도록 하겠다.

Observer Pattern

옵저버 라는 단어 그 자체만 보면 알겠지만, 관찰 대상이 존재한다는 의미다.
SNS에서 팔로잉 하고 있는 인플루언서의 새 포스팅에 대한 안내를 받는 기능은 대표적인 옵저버 패턴의 예시다.

옵저버가 특정한 이벤트를 인지하면 각 옵저버에게 call back, 즉 해당 이벤트에 대한 또 다른 이벤트들이 진행된다. 옵저버의 입장에서는 자신을 바라보고 있는 또 다른 옵저버가 있을 수 있다. 즉 하나의 이벤트를 통해 여러가지 객체들에 영향을 끼쳐야 하는 상황에서 이 패턴을 사용하는 게 유리하다.

SNS 뿐만 아니라 MVC 패턴에서도 사용된다. 각각의 분리 된 Model, View, Controller 각각은 서로를 바라보고 있는 입장이다. 여기에 더 해서 Model과 View 사이의 연결을 느슨하게 처리할 때 옵저버 패턴이 사용된다. 모델에서 어떤 일이 일어난다면 옵저버가 이걸 확인하고 뷰의 내용을 바꾼다든지...중개자가 있으니 직접적인 연결이 일어나지 않고도 변화를 줄 수 있다.

Observer Pattern 예시 코드

요즘 보고 있는 면접을 위한 CS 전공지식 노트 라는 책에 좋은 예시가 있어서 참고해보았다.
http://www.yes24.com/Product/Goods/108887922
관심이 있다면 꼭 읽어보기를...고학년 학부생에게 매우 유용하고 좋은 책이라 생각한다.

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(); 
}

Subject를 기반으로 Topic 이라는 클래스를 만들 것이고 Observer를 기반으로 Topic Subscriber 클래스를 생성 할 것이다. 특정한 주제에 대해 구독하고 있는 Observer들이라는 상황을 한번 가정 해 보겠다.


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();
    }
}

register 는 만약 특정한 옵저버가 해당 주제를 구독하고 있는 게 아니라면, 구독자 리스트에 해당 옵저버를 저장하는 역할을 한다.
나머지 unregister, getUpdate는 크게 어려운 게 없으니 설명을 생략하겠으나, notifyObservers, postMessage 와 같은 메소드를 주목 해 보자.

postMessage 메소드에서는 입력 받은 메시지를 Topic 클래스의 멤버 변수에 저장함과 동시에 notifyObservers를 통해 해당 Topic을 구독하고 있는 모든 observer들에게 observer 객체의 update 메소드를 실행시킴으로써 값을 전달한다.

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); 
    } 
}

update 명령을 하달받은 해당 객체들은 자신이 구독하고 있던 topic 객체에서 message를 하달받아 출력한다.

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

실제 활용 사례를 보도록 하겠다. 하나의 토픽에 대해 a, b, c라는 Observer를 등록시키고 아무무가 OP 챔피언이라는(?) 메시지를 전달했다.

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

topic 객체의 데이터 변화를 감지하고 세 가지 객체가 반응 하는 것을 확인 할 수 있다.

간단한 예시지만 학부 수준에서 이 패턴을 이해하는 데 정말 도움이 되는 코드라 참고 해 보았다.

Proxy Pattern

프록시 서버라는 말 들어 본 적 있을 것이다. 실제로 프록시 서버의 역할은 클라이언트가 해당 프록시 서버를 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있도록 역할한다. 프록시란 단어 그 자체를 해석하면 대리자, 중재자, 대변인 다양한 의미가 있겠지만, 어쨌든 프록시 패턴 또한 마찬가지로 다른 객체에게 무슨 일을 대신 맡길 때 사용한다.

특정한 객체가 어떤 로직에 사용된다고 치자. 그런 로직 상에서 다른 대리자 객체를 투입하여 처리하는 패턴이다. 클라이언트 입장에서는 실제로 실행시킬 클래스에 대한 객체가 데이터를 처리했는 지, 프록시 객체가 처리했는 지 알 수 없다.

이렇게 흐름을 제어하는 데 쓰이지만 결과값을 조작하거나 변경이 되서는 안 됀다. 사실 그렇게 할 것 같으면 이 패턴을 쓸 이유가 하나도 없겠지.

프록시 패턴의 특징은 정리하면 아래와 같다.

  • 실제 객체와 같은 이름의 메소드를 구현하고 인터페이스를 사용한다.
  • 실제 객체에 대한 참조 변수를 갖는다.
  • 실제 객체와 같은 이름을 가진 메소드를 호출하고 그 값을 클라이언트에 돌려준다.
  • 실제 객체의 메소드 호출 전 후에도 별도의 로직을 수행 할 수도 있다. 독립 된 객체로써 존재한다.

프록시 패턴의 장점으로는 대표적으로 두 가지가 있다. 먼저 실제 객체가 명령을 수행하기 전에 전처리를 하거나 기본 객체를 캐싱 할 수 있다는 것이다. 그 다음으로 실제 객체를 수정하지 않고 추가적인 기능을 삽입 할 수 있다는 것이다. 정리하면 객체 간의 Dependency를 줄여주는 역할을 한다는 것이다.

또한 보안성이 좋다는 장점도 있다. 명령을 내린 입장에선 실제로 명령이 어떻게 처리되는 지 전혀 알 수 없으니까.

가독성이 떨어진다는 단점 또한 존재한다. 처리가 여러모로 복잡해지기 때문에.

Proxy Pattern 예제 코드

interface Image {
    public void displayImage();
}

예를 들어, Image라는 인터페이스가 하나 존재한다고 가정 해 보자.

//on System A
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) {
        this.filename = filename;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        System.out.println("Loading   " + filename);
    }

    @Override
    public void displayImage() {
        System.out.println("Displaying " + filename);
    }
}

A라는 시스템에 종속 되어있는 RealImage 클래스다. 이미지 이름을 출력하는 displayImage 메소드와 시스템 상의 디스크에서 이미지를 불러오는 loadImageFromDisk 메소드가 존재한다.

//on System B
class ProxyImage implements Image {
    private String filename;
    private Image image;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void displayImage() {
        if (image == null)
           image = new RealImage(filename);

        image.displayImage();
    }
}

B라는 시스템에 종속 되어있는 ProxyImage 클래스다. 이 또한 마찬가지로 displayImage 메소드를 Override 하여 구현하고 있다. 하지만 조금 차이가 있다면 해당 객체가 가지고 있는 Image 멤버 객체가 null이라면, RealImage 객체를 생성하여 이미지 파일을 불러다가 가져온다. RealImage 클래스의 생성자를 보면 알겠지만 해당 객체가 생성되면서 자신이 종속 된 시스템에서 이미지를 가져오도록 되어있다.

class ProxyExample {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");

        image1.displayImage(); // loading necessary
        image2.displayImage(); // loading necessary
    }
}

위와 같이 main 코드 상에서 두 가지 ProxyImage 객체를 생성하여 displayImage 메소드를 실행시킨다면 결과는 아래와 같다.

Loading    HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Loading    HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2

RealImage 객체는 ProxyImage에 종속되어 있는 상태다. 클라이언트는 이러한 RealImage에 직접적으로 명령을 하달하지 않았음에도 ProxyImage의 중간 처리를 통해 마치 RealImage를 불러와서 쓰고있는 것과 같은 결과를 나타 낼 수 있다.

Iterator Pattern

Iterator가 무엇인 지 잘 생각해보자. JAVA로 코딩을 할 때 컨테이너 간에 데이터들에 접근하고자 할 때 Iterator를 사용한다.

하지만 ArrayList가 아닌 한 컨테이너들의 요소들은 서로 연결되어 있기는 하지만, 바로 옆에 위치 해 있는 상황은 아니다. Iterator는 이러한 상황에서 컨테이너들의 요소들을 마치 For문 과 같은 반복문으로 인덱스 별 탐색을 하듯이 요소들에 접근하게 해 준다.

이 패턴은 따로 예시 코드를 작성하진 않겠다. 대신 Iterator를 활용 한 예시를 하나 남겨두겠다. C++이든 파이썬이든, JAVA든 상당히 많이 쓰는 기능이고 이러한 패러다임 덕에 다양한 자료구조를 조금이라도 편하게 쓸 수 있다는 사실 정도만 알고 가면 충분 해 보인다.

List<String> list = Arrays.asList("자바", "파이썬", "스프링", "장고");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
	String name = iterator.next();
	System.out.println(name);
}
		
for (String s : list) {
	System.out.println(s);
}

Revealing Module Pattern

노출 모듈 패턴이라고도 부른다. 이 패턴은 즉시 실행 함수를 통해 private 와 public 같은 접근 제어자를 만드는 패턴이다.

Java에서는 모르겠다만, Javascript만 해도 따로 접근 제어자가 없다보니 이러한 패턴이 필요 해 진다.

Revealing Modlue Pattern 예시 코드

const pukuba = (() => {
	const a = 1
  	const b = () => 2
  	const public = {
    	c : 2,
      	d : () => 3
    }
    return public
})()

console.log(pukuba)
console.log(pukuba.a)
{ c : 2, d : [Function : d] }
undefined

return public 처리를 함으로써 pukuba 내의 a, b에 접근하지 못 하도록 처리 해 둔 예시이다.

Outro

다음 포스팅은 MVC, MVP, MVVM 에 대한 차이점을 알아보도록 하겠다.
총 세 가지 포스팅을 통해 10 가지 패턴들에 대해 알아 보게 될 것이다.

실제로는 더 많은 패턴들이 존재하고 많이 연구되고 있겠지만, 학부 생 수준에서 이해해야 하는 건 이 정도까지지 않을 까 해서 범위를 좁게 잡아보았다.

그리고 필자는 스스로가 이해를 해야 글을 쓰는 편이라...

Reference

https://ko.wikipedia.org/wiki/%EB%B0%98%EB%B3%B5%EC%9E%90_%ED%8C%A8%ED%84%B4
https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%ED%8C%A8%ED%84%B4
https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4

profile
블로그 이전합니다 : https://swj-techblog.vercel.app/

0개의 댓글