천천히 빛나는

디자인패턴: 전략패턴 본문

카테고리 없음

디자인패턴: 전략패턴

까만콩 •ᴥ• 2024. 10. 31. 01:21

전략 패턴

실행 중에 알고리즘을 선택할 수 있게 하는 디자인 패턴이다.

전략 패턴은 특정한 계열의 알고리즘들을 정의하고 각 알고리즘을 캡슐화하며 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.

변경 가능성이 큰 부분을 분리하고, 인터페이스를 이용하여 구현체로 캡슐화하여 실행 중 해당 구현체만 setter 등의 메서드로 상호 교체 가능하게 만드는 것이다.

예시

<꽉꽉 오리>라는 오리 시뮬레이션 게임이 있다. 이 게임에는 다양한 오리들이 존재한다. 오리들은 헤엄도 치고 꽥꽥 소리도 낸다.

현재 이 게임은 Duck이라는 슈퍼클래스와 이 클래스를 상속받은 다양한 종류의 오리 클래스가 존재한다.

Duck 이라는 슈퍼클래스에는 헤엄을 치는 swim() 메소드, 꽥꽥 소리를 치는 quack() 메소드, 그리고 각 오리의 생김새를 나타내는 display() 메소드가 있다.

이 때, 오리의 생김새는 오리의 종류마다 다르므로 display() 메소드는 추상 메소드이다.

그리고 슈퍼클래스 Duck을 확장한 구체적인 오리들, MallardDuck 과 ReadheadDuck 은 각각의 생김새가 다르므로 display() 메소드를 별도로 구현하고 있다.

⇒ 이때 오리가 날 수 있도록 하라는 지시가 들어왔다면?

해결방법 (1)

Duck 클래스에 fly()만 추가하면 모든 오리가 이걸 상속받아서 날 수 있게 된다.

하지만 장난감 오리도 날아다니게 된다. 몇몇 서브클래스의 오리만 날아야하는데 모든 클래스의 오리들이 날게 된다.

RubberDuck.java

quack() { // 삑삑 }
display() { // 장난감 고무 오리 }
fly() {
// 아무것도 하지 않도록 오버라이드
}

서브클래스가 10억개라면 다 확인하고 상황에 맞게 오버라이딩을 해야 하는 문제가 발생한다.

문제점 (1)

  • 서브 클래스에서 코드가 중복된다.
  • 실행 시에 특징을 바꾸기 힘들다.
  • 모든 오리의 행동을 알기 힘들다.
  • 코드를 변경했을 때 다른 오리들에게 원치 않은 영향을 끼칠 수 있다.

해결방법 (2)

슈퍼클래스의 fly() 메소드가 들어있다는 것이 문제점이었다. 또한 장난감 러버덕 오리는 quac(꽉)이 아닌 bbick(삑)이다.

그래서 fly()와 quack()을 슈퍼클래스에서 빼서 interface로 만들었다.  fly() 메소드가 들어있는 Flyable 인터페이스와 quack() 메소드가 들어있는 Quackable 인터페이스가 만들어진 것을 볼 수 있다.

일부 문제점은 해결할 수 있지만, 코드를 재사용하지 않으므로 코드 관리에 커다란 문제가 생긴다. interface를 사용하는 class에서 해당 코드를 전부 구현해줘야 한다.

그래서 날아다니는 동작의 디테일이 변경되면 모든 서브클래스들의 fly() 메소드를 전부 수정해야 한다.

최종 해결 방안

  • 상속 → 모든 서브 클래스에서 한 가지 행동만 하게 된다. 오리마다 행동이 바뀌게 하라면 너무 많은 관리가 필요하다.
  • 인터페이스 → 코드를 재사용할 수 없어서 한가지 행동을 바꿀 때마다 그 행동이 정의된 모든 서브 클래스를 찾아서 수정해야 한다.

바뀌는 부분은 fly(), quack() 메소드이고 나머지는 그렇지 않은 부분이라고 하자.

fly()와 quack()을 Duck 클래스로부터 분리하려면, 두 개이 메소드 모두 Duck 클래스에서 꺼내서 각 행동을 나타낸 클래스 집합을 새로 만들어야 한다.

이제부터는 각 행동을 인터페이스로 표현하고, 이 인터페이스를 사용해서 행동을 구현한다.

이런 식으로 디자인 하면 다른 형식의 객체에서도 fly와 quack을 재사용 할 수 있다.

가장 중요한 점은 나는 행동과 꽥꽥거리는 행동을 Duck 클래스(또는 그 서브클래스)에서 정의한 메소드를 써서 구현하지 않고 다른 클래스에 위임한다는 것이다.

Duck.java

public abstact class Duck {
		QuackBehavior quackBehavior;
		FlyBehavior flyBehavior;
    // 기타 코드
    
    public void performQuack() {
    	quackBehavior.quack();
    }
}

MallardDuck.java (청둥오리)

public class MallardDuck extends Duck {
	
    public MallardDuck() {
    	quackBehavior = new Quack();
      flyBehavior = new FlyWithWings();
    }
    
		public void display() {
     	System.out.println("저는 청둥오리입니다")
    }
}

여기서는 생성자에서 인스턴스를 만드는 방식이었다. Setter 메소드를 사용해서 동적으로 지정할 수도 있다.

public void setFlyBehavior(FlyBehavior fb) {
    this.flyBehavior = fb;
}