CS 전공지식 정리 - 디자인 패턴과 프로그래밍 패러다임 2. 팩토리 패턴, 전략 패턴, 옵저버 패턴

제훈·2024년 12월 23일

Study

목록 보기
10/30

1 - 2. 팩토리 패턴

정의 : 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴

상위 클래스와 하위 클래스가 분리돼 느슨한 결합을 가진다.

그래서 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없어서 더 많은 유연성을 갖게 된다.

그리고 객체 생성 로직이 따로 있기 때문에 코드를 리팩터링하더라도 한 곳만 고치면 돼 유지보수성이 증가한다.

ex) 카페에서 여러 음료의 레시피마다 구체적인 내용이 들어 있는 하위 클래스가 있어 상위 클래스로 전달되고, 상위 클래스에서 이 레시피들을 토대로 우유 등을 생산하는 생산 공정을 생각하면 된다.


JavaScript에서 팩토리 패턴을 구현한다면 간단하게 new Object()로 구현 가능

const num = new Object(5)
const str = new Object('jojehun')
num.constructor.name; // Number
str.constructor.name; // String

숫자가 문자열을 전달함에 따라 다른 타입의 객체를 생성하는 것을 볼 수 있는데 전달받은 값에 따라 다른 객체를 생성하며 인스턴스의 타입을 정한다.

예시를 보자.

class Latte {
    constructor() {
        this.name = "latte"
    }
}

class Americano {
    constructor() {
        this.name = "Americano"
    }
}

class LatteFactory {
    static createCoffee() {
        return new Latte()
    }
}
class AmericanoFactory {
    static createCoffee() {
        return new Americano()
    }
}

const factoryList = { LatteFactory, AmericanoFactory }

class CoffeeFactory {
    static createCoffee(type) {
        const factory = factoryList[type]
        return factory.createCoffee()
    }
}

const main = () => {
    const coffee = CoffeeFactory.createCoffee("LatteFactory")
    console.log(coffee.name) // latte
}

main()

CoffeeFactory 라는 상위 클래스가 중요한 뼈대면
하위 클래스인 LatteFactory, AmericanoFactory로 구체적인 내용을 담고 있다.

이 때 의존성 주입으로 볼 수 있는데 CoffeeFactory에서 LatteFactory의 인스턴스를 생성하는 것이 아닌 LatteFactory에서 생성한 인스턴스를 CoffeeFactory에 주입하고 있기 때문이다.

그리고 정적 메소드인 static 으로 createCoffee()를 정의했다.

정적 메소드를 쓰면 클래스의 인스턴스 없이 호출이 가능해서 메모리를 절약할 수 있다...

하지만 무작정 정적 메소드로 도배를 하면 애플리케이션 실행 ~ 종료 동안 해당 정보를 static 메모리 영역에 저장하는데 그것 또한 절약한다고 보기에는 어렵다.


1 - 3. 전략 패턴

전략 패턴(strategy pattern) = 정책 패턴(policy pattern)

객체의 행위를 바꾸고 싶은 경우 ‘직접’ 수정하지 않고 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴


1 - 4. 옵저버 패턴

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

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

주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 한다.

ex) 트위터 : 내가 어떤 누군가를 팔로우했을 때 해당 사용자가 포스팅을 올리면 알림이 나에게 와야 한다.

주로 이벤트 기반 시스템에 사용하며 MVC 패턴에도 사용된다.

주체라고 볼 수 있는 모델(model)에서 변경 사항이 생기면, 옵저버인 뷰에 알려주고, 이를 기반으로 컨트롤러(controller) 등이 작동하는 것이다.


Java 에서의 옵저버 패턴

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

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("Hello"); 
    }
}
/*
Message sended to Topic: Hello
a:: got message >> Hello
b:: got message >> Hello
c:: got message >> Hello
*/

topic을 기반으로 옵저버 패턴을 구현

-> topic이 주체이자 객체가 된다.

class Topic implements Subject를 통해 Subject interface를 구현
Observer a = new TopicSubscriber("a", topic);으로 옵저버를 선언할 때 해당 이름과 어떠한 토픽의 옵저버가 될 것인지를 정한다.


JavaScript에서의 옵저버 패턴

프록시 객체를 통해 구현할 수도 있는데
프록시(proxy) 객체 : 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체

  • target: 프록시할 대상
  • handler: 프록시 객체의 target 동작을 가로채서 정의할 동작들이 정해져 있는 함수
const handler = {
    get: function(target, name) { 
        return name === 'name' ? `${target.a} ${target.b}` : target[name]
    }
}
const p = new Proxy({a: 'JoJeHun', b: 'is a man'}, handler)
console.log(p.name) // JoJeHun is a man

new Proxy로 선언한 객체의 a와 b라는 속성에 특정 문자열을 담아서 handler에 name이라는 속성에 접근할 때는 a와 b라는 것을 합쳐서 문자열을 만들게끔 했다.

p라는 변수에 name이라는 속성을 선언하지 않았는데도 p.name으로 name 속성에 접근하려고 할 때, 그 부분을 가로채 문자열을 만들어 반환하는 것을 볼 수 있다.

다음은 프록시 패턴이다.

profile
백엔드 개발자 꿈나무

0개의 댓글