상세 컨텐츠

본문 제목

[Design Pattern] Strategy Pattern

JAVA

by jeonghojin 2023. 3. 23. 02:12

본문

Strategy Pattern

행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴
- 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결할 수 있게 하는 디자인 패턴

Strategy :
- 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시
ConcreteStrategy :
- 스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스
Context :
- 스트래치지 패턴을 이용하는 역할을 수행한다.
- 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메서드(' 집약관계 ' ) 를 제공한다.

 

행위 패턴 :
- 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
- 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를 최소화하는 것에 중점을 둔다.
집약 관계 :
- 참조값을 인자로 받아 필드를 세팅하는 경우
- 전체 객체의 라이프타임과 부분 객체의 라이프 타임은 독립적이다.
- 즉, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.

예를들어, 게임 캐릭터를 만든다고 가정하자.
Robot이라는 수퍼 클래스를 만들고, 이 클래스를 확장하여 다른 로봇 클래스들을 만든다.
각각의 로봇의 모양새는 다르므로 display() 메서드는 추상 메서드로 선언한다.

public abstract class Robot {

    public void run() {
        System.out.println("run~");
    }

    public abstract void display();
}

 

public class RobotA extends Robot {
    @Override
    public void display() {
        System.out.println("A display~");
    }
}
public class RobotB extends Robot {

    @Override
    public void display() {
        System.out.println("B display~");
    }
}

 
수퍼 클래스 Robot에 fly() 메서드를 추가한다.
수퍼 클래스 Robot을 상속받은 클래스들은 모두 fly() 메서드를 사용할 수 있다.
그러나 fly() 기능이 필요없는 클래스도 fly() 메서드를 사용할 수 있게 된다.

public abstract class Robot {

    public void run() {
        System.out.println("run~");
    }
    
    public void fly() {
        System.out.println("fly~");
    }

    public abstract void display();
}

 
 
ARobot 클래스에서는 fly() 기능이 필요하지 않다. 그러므로 오버라이드 하여 처리한다.
앞으로 추가적인 클래스들이 생길 때마다 또는 특정 클래스에만 추가적인 기능을 부여할 경우, 일일이 검토하여 구분하여야 한다.

public class RobotA extends Robot {
    @Override
    public void display() {
        System.out.println("A display~");
    }
    public void fly() {
        // 아무것도 하지 않도록 오버라이드
    }

}

인터페이스를 사용

public abstract class Robot {


    public abstract void display();
}

 
인터페이스

public interface Runnable {

    void run();
}
public interface Flyable {
    void fly();
}

 

public class RobotA extends Robot implements Runnable{
    @Override
    public void display() {
        System.out.println("A display~");
    }

    @Override
    public void run() {
        System.out.println("run~");
    }
}
public class RobotB extends Robot implements Runnable, Flyable {

    @Override
    public void display() {
        System.out.println("B display~");
    }

    @Override
    public void fly() {
        System.out.println("fly~");
    }
    @Override
    public void run() {
        System.out.println("run~");
    }
}

 

다음과 같이 구현한다면 Flyable, Runnable 인터페이스를 일일이 다 구현해주어야 한다.

모든 코드들은 하드코딩하게 되고, 재사용할 수 없게 된다.

 


 

1. 달라지는 부분을 분리한다.

2. 구현이 아닌 인터페이스 맞춰서 프로그래밍 한다.

 

 

각 행동별 인터페이스를 생성한다.

각각의 구체적인 행동은 인터페이스를 상속받아 구현한다.

 

Interface MoveBehavior

public interface MoveBehavior {
    void move(String name);
}
public class FlyBehavior implements MoveBehavior {
    @Override
    public void move(String name) {
        System.out.println(name+ " fly~");
    }
}
public class RunBehavior implements MoveBehavior {
    @Override
    public void move(String name) {
        System.out.println(name + " run ~");
    }
}

 

Interface AttackBehavior

public interface AttackBehavior {
    void attack(String name);
}
public class MissileBehavior implements AttackBehavior {
    @Override
    public void attack(String name) {
        System.out.println(name+ " missile ~");
    }
}
public class PunchBehavior implements AttackBehavior {
    @Override
    public void attack(String name) {
        System.out.println(name+ " punch ~");
    }
}

 

Abstract Robot

public abstract class Robot {

    private String name;
    private MoveBehavior moveBehavior;
    private AttackBehavior attackBehavior;

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

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

    public void performMove() {
        moveBehavior.move(name);
    }

    public void setMoveBehavior(MoveBehavior moveBehavior) {
        this.moveBehavior = moveBehavior;
    }

    public void setAttackBehavior(AttackBehavior attackBehavior) {
        this.attackBehavior = attackBehavior;
    }

}

 

 Class ARobot

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

 

Class BRobot

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

 

Main

public class Main {
    public static void main(String[] args) {
        ARobot aRobot = new ARobot("ARobot");
        BRobot bRobot = new BRobot("BRobot");

        aRobot.setAttackBehavior(new PunchBehavior());
        aRobot.setMoveBehavior(new RunBehavior());

        bRobot.setAttackBehavior(new MissileBehavior());
        bRobot.setMoveBehavior(new FlyBehavior());

        aRobot.performMove();
        aRobot.performAttack();

        bRobot.performMove();
        bRobot.performAttack();
    }
}

 

 

 다음과 같이 디자인하게 되면 행동(기능)을 추가할 때, 기존 코드는 건드리지 않고 새로운 행동(기능)을 추가할 수 있게 된다.

'인터페이스' 라는 것은 꼭 interface 구조를 지칭하는 것이 아닌 어떤 상위 형식(super type)에 맞춰 다형성을 활용하는 것이다.
객체의 변수 타입을 지정할 때 상위 형식을 사용한다면, 실제 그 객체에 변수를 대입할 때에는 어떤 하위객체든지 올 수 있다.

Robot 클래스에서 어떤 행동을 할지 정의하지 않고, 객체를 생성할 때  참조되는 객체의 행동을 정의할 수 있도록 그 역할을 위임한다.

 
 

is-a 관계보다 has-a 관계

상속보다는 구성을 활용한다.

https://zangzangs.tistory.com/44


Strategy Pattern 장/단점

장점 :
- 컨텍스트 코드의 변경없이 새로운 전략을 추가할 수 있다. ( 컨텍스트에 대한 OCP 원칙을 만족)
 
단점 :
- 사용되는 전략이 적다면, 복잡성만 늘어나게 된다.


 
참고 : 

더보기

'JAVA' 카테고리의 다른 글

[Design Pattern] Builder Pattern  (0) 2023.03.25
[Design Pattern] Decorator Pattern  (0) 2023.03.24
[Design Pattern] Abstract Factory Pattern  (0) 2023.03.22
[Design Pattern] Command Pattern  (0) 2023.03.21
[Design Pattern] Factory Method Pattern  (0) 2023.03.19

관련글 더보기