[Design Pattern] Strategy Pattern

Roy·2024년 1월 19일

Design Pattern

목록 보기
5/5

개요

Strategy Pattern은 Design Pattern의 꽃이라 불린다.
그만큼 자주 사용되는 패턴이기도 하고, Spring에서도 사용된다.
어떤 모드에 따라 동일한 메소드가 달라지는 패턴을 객체지향적으로 프로그래밍하는 방법이다.

흔히 클래스나 함수가 특정 변수에 의존하여, 다르게 동작하도록 코드를 짜곤 하는데,
아래는 StarCraft 2에 등장하는 유닛을 클래스로 작성하고, 이를 불러오는 코드이다.

수정 전 코드

Unit.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.before;

public class Unit {

    private String name;

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

    public void attack() {
        if (name == "Marine") {
            System.out.println("Shoot Gauss Rifle");
        } else if (name == "Zealot") {
            System.out.println("Swing Psionic Blades");
        } else if (name == "Hydralisk") {
            System.out.println("Spit Needle Spines");
        }
    }

    public void defend() {
        if (name == "Marine") {
            System.out.println("Raise Combat Shield");
        } else if (name == "Zealot") {
            System.out.println("Activate Psionic Shield");
        } else if (name == "Hydralisk") {
            System.out.println("Burrow");
        }
    }
}

Client.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.before;

public class Client {

    public static void main(String[] args) {
        Unit unit = new Unit("Marine");
        unit.attack();
        unit.defend();
    }
}

문제점

1. 새로운 Unit이 생길 때마다 Unit.java를 수정해야 한다. (OCP 위반)
StarCraft 2 Unit은 어림 잡아 60기 쯤 된다.
유닛들을 추가할 때마다 Unit.java를 뜯어고쳐야 하고, if-else 문은 끝없이 생겨날 것이다.

위 예시는 name에 의존하는 메소드/로직이 아주 단순하지만, 실제로 서비스에 사용되는 코드는 더 복잡할 것이다.
Unit 내부 로직이 name 변수에 의존하여, 모드가 바뀌는 것을 상상해보면,
name마다 어떤 모드로 바뀌는 지 설명도 필요할 것이다.
주석으로 그런 것들을 설명할 수 있겠지만, 차라리 이를 코드로 구현하면, 코드 자체로 동작과 명세를 완성할 수 있다.
(여기서 동작이란, 프로그램이 돌아가게 하는 것이고, 명세란 코드에 대한 설명이다.)

2. 코드 가독성이 나빠진다.
if-else 문이 생겨남에 따라 코드를 직관적으로 이해할 수 없게 된다.
코드를 돌려봐야만 결과를 알 수 있거나 하는 식이다.

Strategy Pattern 적용

여기에 Strategy Pattern을 적용해보자.
아래는 Strategy Pattern의 UML이다.

  • Client는 각 ConcreteStrategy들과 Context를 알고 있어야 한다. (위의 예시로는, Marine/Zealot/Hydralisk가 있다는 것.)

우선, Unit에서 Name을 분리한다.
Name은 interface로 구현하고, Unit은 Name에 의존한다.

Unit.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.after;

public class Unit {

    private Name name;

    public Unit(Name name) {
        this.name = name;
    }

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

    public void defend() {
        name.defend();
    }
}

Name.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.after;

public interface Name {

    public void attack();

    public void defend();
    
}

이제 Unit의 전략에 해당하는 Marine, Zealot, Hydralisk를 만든다.
각 전략은 Name을 구현하는 클래스가 된다.

Marine.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.after;

public class Marine implements Name {
    @Override
    public void attack() {
        System.out.println("Shoot Gauss Rifle");
    }

    @Override
    public void defend() {
        System.out.println("Raise Combat Shield");
    }
}

Zealot.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.after;

public class Zealot implements Name {
    @Override
    public void attack() {
        System.out.println("Swing Psionic Blades");
    }

    @Override
    public void defend() {
        System.out.println("Activate Psionic Shield");
    }
}

Hydralisk.java
-------------------------------------------------------------------------------------------------------------
package StrategyPattern.after;

public class Hydralisk implements Name {
    @Override
    public void attack() {
        System.out.println("Spit Needle Spines");
    }

    @Override
    public void defend() {
        System.out.println("Burrow");
    }
}

마지막으로 Client 코드는 각 전략을 알고 있는 상태로 이를 호출한다.

package StrategyPattern.after;

public class Client {

    public static void main(String[] args) {
        Unit unit = new Unit(new Marine());
        unit.attack(); // Shoot Gauss Rifle
        unit.defend(); // Raise Combat Shield
    }
}
profile
Backend Engineer

0개의 댓글