[Design Pattern] 스트레티지 패턴(Strategy Pattern)

in·2023년 10월 17일
0

Design Pattern

목록 보기
7/8

📌 스트레티지 패턴(Strategy Pattern)

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

Strategy
인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법을 명시

ConcreteStrategy
스트래티지 패턴에서 명시한 알고리즘을 실제로 구현한 클래스

Context
스트래티지 패턴을 이용하는 역할 수행

필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 setter 메소드(집약 관계)를 제공

✔️ 집약 관계

  • 참조값을 인자로 받아 필드를 세팅하는 경우
  • 전체 객체의 라이프타임과 부분 객체의 라이프 타임은 독립적
  • 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않음

예제

public abstract class Robot {
  private String name;
  public Robot(String name) { this.name = name; }
  public String getName() { return name; }
  // 추상 메서드
  public abstract void attack();
  public abstract void move();
}

public class TaekwonV extends Robot {
  public TaekwonV(String name) { super(name); }
  public void attack() { System.out.println("I have Missile."); }
  public void move() { System.out.println("I can only walk."); }
}

public class Atom extends Robot {
  public Atom(String name) { super(name); }
  public void attack() { System.out.println("I have strong punch."); }
  public void move() { System.out.println("I can fly."); }
}
public class Client {
  public static void main(String[] args) {
    Robot taekwonV = new TaekwonV("TaekwonV");
    Robot atom = new Atom("Atom");

    System.out.println("My name is " + taekwonV.getName());
    taekwonV.move();
    taekwonV.attack();

    System.out.println()
    System.out.println("My name is " + atom.getName());
    atom.move();
    atom.attack();
  }
}

❗️ 문제점
1. 기존 로봇의 공격과 이동 방법을 수정하는 경우

  • Atom이 날 수는 없고 걷게만 만들고 싶다면?
  • TaekwonV를 날게 하려면?
public class Atom extends Robot {
  public Atom(String name) { super(name); }
  
  public void attack() { System.out.println("I have strong punch."); }
        
  public void move() { System.out.println("I can only walk."); } // 수정
}

➡️ 새로운 기능으로 변경하려고 기존 코드의 내용을 수정해야 하므로 OCP에 위배
TaekwonV와 Atom의 move() 메소드의 내용이 중복(중복 상황은 많은 문제를 야기하는 원인)
걷는 방식에 문제가 있거나 새로운 방식으로 수정하려면 모든 중복 코드를 일관성 있게 변경해야함
 
2. 새로운 로봇을 만들어 기존의 공격(attack) 또는 이동 방법(move)을 추가하는 경우

  • 새로운 로봇으로 Sungard를 만들어 TaekwonV의 미사일 공격 기능을 추가하려면?
public class Sungard extends Robot {
  public Sungard(String name) { super(name); }
  public void attack() { System.out.println("I have Missile."); } // 중복
  public void move() { System.out.println("I can only walk."); }
}

➡️ TaekwonV와 Sungard 클래스의 attack() 메소드의 내용 중복

  • 현재 시스템의 캡슐화 단위가 Robot 자체이므로 로봇 추가하기 쉬움
  • 새로운 로봇인 Sungard에 기존의 공격 또는 이동 방법을 추가하거나 변경하려고 하면 문제 발생

❓ 해결책
➡️ 무엇이 변화되었는지 찾은 후에 이를 클래스로 캡슐화

  • 로봇 예제에서 변화되면서 문제를 발생시키는 요인은 이동/공격 방식의 변화
  • 이를 캡슐화하려면 외부에서 구체적인 이동/공격 방식을 담은 구체적인 클래스 은닉해야함

  • Robot 클래스가 이동/공격 기능을 이용하는 클라이언트 역할 수행

    • 구체적인 이동/공격 방식이 MovingStrategy와 AttackStrategy 인터페이스에 의해 캡슐화
    • 이 인터페이스들이 일종의 방화벽 역할을 수행해 Robot 클래스의 변경 차단
  • 스트레티지 패턴을 이용하면 새로운 기능의 추가(이동, 공격)가 기존 코드에 영향을 미치지 못하게 하므로 OCP를 만족하는 설계

    • 변경된 구조에서는 외부에서 로봇 객체의 이동, 공격 방식을 임의대로 바꾸도록 해주는 setter 메소드가 필요
    • setMovingStrategy, setAttackStrategy
    • 상속 대신 집약 관계를 이용했기 때문에 변경 가능

Robot 클래스

public abstract class Robot {
private String name;
private AttackStrategy attackStrategy;
private MovingStrategy movingStrategy;

public Robot(String name) { this.name = name; }
public String getName() { return name; }
public void attack() { attackStrategy.attack(); }
public void move() { movingStrategy.move(); }

// 집약 관계, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.
// setter 메서드
public void setAttackStrategy(AttackStrategy attackStrategy) {
  this.attackStrategy = attackStrategy; }
public void setMovingStrategy(MovingStrategy movingStrategy) {
  this.movingStrategy = movingStrategy; }
}

구체적인 Robot 클래스

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

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

공격, 이동 기능에 대한 인터페이스와 구체적인 클래스

// 인터페이스
interface AttackStrategy { public void attack(); }
// 구체적인 클래스
public class MissileStrategy implements AttackStrategy {
  public void attack() { System.out.println("I have Missile."); }
}
public class PunchStrategy implements AttackStrategy {
  public void attack() { System.out.println("I have strong punch."); }
}
// 인터페이스
interface MovingStrategy { public void move(); }
// 구체적인 클래스
public class FlyingStrategy implements MovingStrategy {
  public void move() { System.out.println("I can fly."); }
}
public class WalkingStrategy implements MovingStrategy {
  public void move() { System.out.println("I can only walk."); }
}

Client에서의 사용

public class Client {
public static void main(String[] args) {
  Robot taekwonV = new TaekwonV("TaekwonV");
  Robot atom = new Atom("Atom");

  /* 수정된 부분: 전략 변경 방법 */
  taekwonV.setMovingStrategy(new WalkingStrategy());
  taekwonV.setAttackStrategy(new MissileStrategy());
  atom.setMovingStrategy(new FlyingStrategy());
  atom.setAttackStrategy(new PunchStrategy());

  /* 아래부터는 동일 */
  System.out.println("My name is " + taekwonV.getName());
  taekwonV.move();
  taekwonV.attack();

  System.out.println()
  System.out.println("My name is " + atom.getName());
  atom.move();
  atom.attack();
 }
}

정리

Strategy Pattern을 활용하면 로직을 독립적으로 관리하는 것이 편해진다.
로직에 들어가는 '행동'을 클래스로 선언하고 인터페이스와 연결하는 방식으로 구성

[참고 자료]

🔗링크
🔗링크

0개의 댓글