행위 디자인 패턴

Chang-__-·2023년 4월 10일
0

디자인패턴

목록 보기
3/3

전략 패턴 (Strategy Pattern)

GOF의 디자인 패턴에서는 다음과 같이 정의하고 있다.

동일 계열의 알고리즘들을 정의하고,
각 알고리즘을 캡슐화하며,
이 알고리즘들을 해당 계열 안에서 상호교체가 가능하도록 만든다.
알고리즘을 사용하는 클라이언트와 상관없이 독립적으로 알고리즘을 다양하게 변경할 수 있게 한다.

간단히 말해서 객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴입니다.

예를들어, 기차(Train) 버스(Bus) 클래스가 있고, 이 두 클래스는 Movable 인터페이스를 구현했다고 가정해보겠다.

class Movable {
    move(){}
}

class Train extends Movable {
    move() {
        console.log('선로를 따라 이동.')
    }
}

class Bus extends Movable {
    move(){
        console.log('도로를 통해 이동.')
    }
}

class Client {
    go() {
        const train = new Train();
        const bus = new Bus();

        train.move();
        bus.move();
    }
}

시간이 지나 버스도 선로를 통해서 움직이는 버스가 있다고 가정해보면 Bus 클래스를 수정해야한다.
이는 OCP(Open-Closed Principle) 원칙에 위배된다. OCP 에 의거하면 move() 를 수정하지 않으면서 행위가 수정되어야 하지만 지금은 Bus의 move() 메서드를 직접수정했다.

또한 이런 패턴을 사용해서 시스템이 확장되어 오토바이, 택시 등이 추가된다고 할때 모두 버스와 같이 move() 함수를 사용할텐데 오토바이, 택시의 move() 일일히 메서드를 수정해야한다.

그래서 이 부분을 전략패턴으로 바꾸면 다음과 같다.

class MovableStrategy {
    move();
}
class RailLoadStrategy extends MovableStrategy{
     move(){
        console.log("선로를 통해 이동");
    }
}
class LoadStrategy extends MovableStrategy{
     move() {
         console.log("도로를 통해 이동");
    }
}

class Moving {
    movableStrategy;

    move(){
        this.movableStrategy.move();
    }

    setMovableStrategy(movableStrategy){
        this.movableStrategy = movableStrategy;
    }
}
class Bus extends Moving{

}
class Train extends Moving{

}

기차와 버스 같은 운송 수단은 move() 메서드를 통해 움직일 수 있다.

그런데 이동 방식을 직접 메서드로 구현하지 않고, 어떻게 움직일 것인지에 대한 전략을 설정하여, 그 전략의 움직임 방식을 사용하여 움직이도록 한다.

그래서 전략을 설정하는 메서드인 setMovableStrategy()가 존재한다.

클라이언트를 사용하면 다음과 같이 사용할수 있다.

class Client {
   go() {
    	const train = new Train();
        const bus = new Bus();

        /*
            기존의 기차와 버스의 이동 방식
            1) 기차 - 선로
            2) 버스 - 도로
         */
        train.setMovableStrategy(new RailLoadStrategy());
        bus.setMovableStrategy(new LoadStrategy());

        train.move();
        bus.move();

        /*
            선로를 따라 움직이는 버스가 개발
         */
        bus.setMovableStrategy(new RailLoadStrategy());
        bus.move();
    }
}

상태 패턴 (State Pattern)

상태 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서, 자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고 상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴이다.

객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의한다.

사실 전략 패턴이랑 굉장히 비슷한데 전략 패턴은 상속 대체용(사용자가 쉽게 알고리즘 전략을 바꿀 수 있도록 유연성을 제공)으로 사용된다. 상태패턴은 조건문(if, switch)을 대체하기 위해 클래스로 사용하는 패턴이다. 다만 무분별한 상태 패턴은 클래스 수의 증가로 인한 단점이 존재한다.

예를 들어 노트북을 켜고 끄는 상황을 가정해보겠다.

class Laptop {
    static ON = "on";
    static OFF = "off";
    powerState = "";

     Laptop(){
        this.setPowerState(Laptop.OFF);
    }

    setPowerState(powerState){
        this.powerState = powerState;
    }

    powerPush(){
        if ("on" === this.powerState) {
            console.log('전원 off')
        }
        else {
            console.log('전원 on')
        }
    }
}

class Client {
    main() {
        const laptop = new Laptop();
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
    }
}

노트북이 on이면 off 로 변경하고, off면 on으로 변경하는 간단한 코드이다.
여기서 노트북에 절전 모드가 추가된다고 가정해보겠다.

class Laptop {
    static ON = "on";
    static OFF = "off";
    static SAVING = "saving";
    powerState = "";

     Laptop(){
        this.setPowerState(Laptop.OFF);
    }

    setPowerState(powerState){
        this.powerState = powerState;
    }

    powerPush(){
        if ("on" === this.powerState) {
            console.log('전원 off')
        }
        else if("saving" === this.powerState){
            console.log('전원 on')
        }
        else {
            console.log('절전모드')
        }
    }
}

class Client {
    main() {
        const laptop = new Laptop();
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
        laptop.setPowerState(Laptop.SAVING);
        laptop.powerPush();
        laptop.setPowerState(Laptop.OFF);
        laptop.powerPush();
        laptop.setPowerState(Laptop.ON);
        laptop.powerPush();
    }
}

여기서 조건문이 하나 추가 되었다고 뭐가 불편하냐고 할 수 있는데, 만약에 수도 없이 조건문이 늘어나고 관리하기가 어려운 로직이 담기면 이는 상태에 따라 하고자 하는 행위를 파악하기가 쉽지 않을것이다.

그럼 상태 패턴을 적용한 코드는 어떻게 동작할까?

class PowerState {
    powerPush();
}

class On extends PowerState{
    powerPush(){
        console.log('전원 ON')
    }
}

class Off extends PowerState{
    powerPush(){
        console.log('전원 Off')
    }
}

class Saving extends PowerState{
    powerPush(){
        console.log('절전 모드')
    }
}

이런식으로 상태에 대한 클래스를 선언한다.

class Laptop{
    powerState;

    constructor(){
        this.powerState = new Off();
    }

    setPowerState(powerState){
        this.powerState = powerState;
    }

    powerPush(){
        this.powerState.powerPush();
    }
}

class Client {
    main(){
        const laptop = new Laptop();
        const on = new On();
        const off = new Off();
        const saving = new Saving();

        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
        laptop.setPowerState(saving);
        laptop.powerPush();
        laptop.setPowerState(off);
        laptop.powerPush();
        laptop.setPowerState(on);
        laptop.powerPush();
    }
}

Laptop 클래스는 분기 처리하는 코드가 사라지고 powerPush() 메서드를 호출하기만 한다.

템플릿 패턴 (Template Pattern)

템플릿 패턴은 여러 단계로 정의된 알고리즘의 뼈대는 그대로 유지한 채, 알고리즘의 구체적인 구현은 여러가지 형태로 확장 가능하도록 만들어주는 패턴이다.
GOF의 디자인 패턴에서 템플릿 메소드 패턴을 다음과 같이 정의하고 있다.

객체의 연산에는 알고리즘의 뼈대만을 정의하고, 알고리즘 각 단계에서 수행할 구체적 처리는 서브클래스 쪽으로 미룬다.
알고리즘의 구조 자체는 그대로 놔둔 채 알고리즘 각 단계처리를 서브클래스에서 재정의할 수 있게 하는 패턴이다.

간단하게 HouseTemplate 클래스를 통해서 템플릿 패턴을 구현해보겠다.

추상클래스인 HouseTemplate의 buildHouse() 메소드에는 다음과 같이 집을 짓는 과정의 뼈대 알고리즘이 정의 되있다.
buildFoundations -> buildPillars -> buildWalls -> buildWindows

구체 클래스인 WoodenHouse, GlassHouse를 살펴보면 추상 클래스 HouseTemplate을 상속받아서 기본 연산 buildWalls와 buildPillars를 오버라이딩하여 WoodenHouse, GlassHouse 각각에 맞는 구현을 제공하고 있다.

class HouseTemplate {
    buildHouse() {
      this.buildFoundations();
      this.buildPillars();
      this.buildWalls();
      this.buildWindows();
    }

    buildFoundations() {
      console.log('Building Foundations')
    }

    buildWindows() {
      console.log('Building Windows')
    }

    buildWalls() {
      throw new Error('You have to build your own walls')
    }

    buildPillars() {
      throw new Error('You have to build your own pillars')
    }
}

class WoodenHouse extends HouseTemplate {
    constructor() {
      super();
    }

    buildWalls() {
      console.log('Building Walls for wooden house')
    }

    buildPillars() {
      console.log('Building Pillars for wooden house')
    }
}

class GlassHouse extends HouseTemplate {
    constructor() {
      super();
    }

    buildWalls() {
      console.log('Building Walls for glass house')
    }

    buildPillars() {
      console.log('Building Pillars for glass house')
    }
}

const woodenHouse = new WoodenHouse();
const glassHouse = new GlassHouse();
woodenHouse.buildHouse();
glassHouse.buildHouse();![](https://velog.velcdn.com/images/ckanywhere/post/3a5d30c7-19eb-4652-8d29-7f5eef74b890/image.png)
![](https://velog.velcdn.com/images/ckanywhere/post/7172cfec-16c8-4566-842a-d8da0a5b4d68/image.png)

0개의 댓글