본문 바로가기

소프트웨어/디자인패턴

[소프트웨어/디자인패턴] 스트레티지 패턴(Strategy Pattern)

스트레티지 패턴은 OCP를 지킬 수 있도록 도와준다.

Context

만약에 Robot이란 추상클래스가 있고 AtomMarin이 이 추상클래스를 상속받아 각각의 공격방법과 이동 방법을 구현한다. 여기까지는 객체지향적인 설계라고 할 수 있는데, 자 그럼 이제부터 AtomMarin을 업그레이드 시켜보자.

<위의 설명의 모델>


Problem

기능 확장의 문제점

Atom은 기존엔 걸을 수 밖에 없었다. 하지만 연구원들이 열심히 노력해서 걷기보다는 날 수 있도록 만들었다. 그렇다면 코드상 아래와 같아진다.

class Atom extends Robot{
    // written something..
    
    void move() {
        // 이전코드
        // System.out.println("I'm walking!");
        
        // 수정코드
        System.out.println("I'm flying!");
    }
}

훌륭하다! OCP은 어떠한 기능이 추가되었을 때 변경없이 확장이 가능해야한다는 것, 하지만 여기서는 이동하는 것에 있어서 걷는 것에서 나는 것으로 기능이 확장되었으나 기존 Atom class의 코드를 수정하였기 때문에 원칙을 위배했다고 할 수 있다.

중복 코드의 문제점

연구원들은 열심히 차세대 로봇(SuperPowerRobot)을 만들었다. 가장 최근의 기술을 쓰고자 Atom의 나는 기술과 Marin의 총쏘는 기술을 선정하여 그 둘의 장점을 합친 최신예 로봇이다. 그 후 몇년이 지나자 아래와 같이 기존의 나는 방법이 바뀌었다.

class Atom extends Robot{
    // written something..
    
    void move() {
        // 이전코드
        // System.out.println("I'm flying!");
        
        // 수정코드
        System.out.println("I'm flying using SuperPowerEngine!");
    }
}

하지만 동일하게 SuperPowerRobot에도 적용해줘야한다.

class SuperPowerRobot extends Robot{
    // written something..
    
    void move() {
        // 이전코드
        // System.out.println("I'm flying!");
        
        // 수정코드
        System.out.println("I'm flying using SuperPowerEngine!");
    }
}

하나의 기능때문에 두 곳의 코드가 수정되었다. 이는 중복코드로써 유지보수성을 떨어트린다.

그럼 어떻게 해결할 수 있을까?

Solution

위와 같은 설계는 중복 코드와 OCP 위반이란 큰 오점을 남겼다. 잘 생각해보면 moveattack는 추상화의 대상이 될 수 있다. moveattack는 로봇을 구성하는 많은 부분 중 하나이다, 즉 속성이 될 수 있다. 이를 추상화하여 인터페이스로 생성한 후 이를 상속받아 각각의 기능을 구현하는 클래스를 생성하여 로봇에 장착하면 위 중복코드의 문제점과 OCP 위반을 제거하며 구현할 수 있다(중복코드가 발생하지 않고, 확장에서도 interface를 구현하는 새로운 클래스만 만들어 사용하는 곳에서 set메서드를 통해 의존성을 주입해주니 각 로봇의 클래스 코드에는 아무런 수정이 발생하지 않는다)

<위의 설명의 모델>

abstract class Robot {
    private String name;
    private AttackStrategy attackStrategy;
    private MoveStrategy moveStrategy;

    public Robot(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAttackStrategy(AttackStrategy attackStrategy) {
        this.attackStrategy = attackStrategy;
    }

    public void setMoveStrategy(MoveStrategy moveStrategy) {
        this.moveStrategy = moveStrategy;
    }

    public void move() {
        moveStrategy.move();
    }

    public void attack() {
        attackStrategy.attack();
    }
}

interface AttackStrategy {
    public void attack();
}

interface MoveStrategy {
    public void move();
}

class Flying implements MoveStrategy {
    @Override
    public void move() {
        System.out.println("I'm flying~~");
    }
}

class Missile implements AttackStrategy {
    @Override
    public void attack() {
        System.out.println("Shoot missiles!");
    }
}

class Flying implements MoveStrategy {
    @Override
    public void move() {
        System.out.println("I'm flying~~");
    }
}

class Punch implements AttackStrategy {
    @Override
    public void attack() {
        System.out.println("Punch to enemy!");
    }
}

class Atom extends Robot {
    public Atom(String name) {
        super(name);
    }
}

class Marin extends Robot{
    public Marin(String name) {
        super(name);
    }
}

class SuperPowerRobot extends Robot {
    public SuperPowerRobot(String name) {
        super(name);
    }
}

class Sample {
    public static void main(String[] args) {
        Robot atom = new Atom("Atom"),
                marin = new Marin("Marin"),
                superRobot = new SuperPowerRobot("Super");

        AttackStrategy
                missile = new Missile(),
                punch = new Punch();

        MoveStrategy
                walking = new Walking(),
                flying = new Flying();


        atom.setAttackStrategy(missile);
        atom.setMoveStrategy(walking);

        marin.setAttackStrategy(punch);
        marin.setMoveStrategy(flying);

        superRobot.setAttackStrategy(missile);
        superRobot.setMoveStrategy(flying);

    }
}

Summary

스트래티지 패턴은 전략을 쉽게 바꿀 수 있도록 해주는 디자인패턴이다, 전략의 의미는 알고리즘, 비즈니스 로직, 일을 수행하는 방식, 장착해야할 아이템 등이 될 수 있다. 롤의 챔피언만 해도 수시로 자신의 아이템 전략을 바꾼다. 이를 단일 클래스로 지정해버리면 바꿀 수 없지만 List<Item>등과 같이 Item을 추상화한 인터페이스 또는 추상클래스로 바꾼다면 충분히 지속적인 변경에도 무리가 없다.