01. 전략 패턴

Mando·2023년 3월 17일
0

디자인 패턴

목록 보기
1/6

오리 시뮬레이션 게임

Duck 슈퍼 클래스를 만든 다음, 그 클래스를 확장해서 서로 다른 종류의 오리를 만들었다.

  • 모든 오리가 quack(꽥꽥 소리를 낼 수 있고), swim(수영할 수 있다) 특징을 가지고 있다.
  • 하지만 고유한 모양을 보여주는 display는 오리마다 다르므로 추상 메서드이다.

오리가 날아야 한다는 기능 추가

이후 오리가 날아야 한다는 기능이 추가되었다.
그래서 Duck 슈퍼 클래스에 fly()메서드를 추가했다.

슈퍼 클래스에 메서드를 추가하면서 문제점 발생**

하지만, 문제점이 발생했다.
몇몇 서브 클래스에서는 fly()(고무 오리)가 나는 행동이 추가되지 않아야 하는데
슈퍼 클래스에 fly()메서드를 추가하면서, 일부 서브클래스에 적합하지 않은 행동이 추가되었다.

코드를 재사용 한다는 관점에서 상속을 잘 사용했다고 생각했는데,
기능을 추가했을 때 유지보수 면에서 좋지 않다.

그래서 슈퍼클래스에서 선언한 fly()를 고무 오리 서브 클래스에서 오버라이드해서 날지 않음으로 변경할 수도 있다.

인터페이스 이용하기

상속이 좋은 방법이 아님을 알게되었다.
또한, 6개월마다 기능이 변경되기로 했는데 정확하게 어떻게 기능이 변경될지는 결정되지 않았다.

상속을 이용한다면, 6개월마다 각 서브클래스에 fly()메서드가 어떻게 적용되었는지 일일히 확인해야 하는데 이는 좋은 방법이 아니다…

그래서 인터페이스르 사용하기로 했다!

fly() 인터페이스가 들어있는 Flyable 클래스를 만들고, 날 수 있는 오리에게만 Flyable 클래스의 fly()를 구현해서 모든 오리가 꽥꽥 울지 않도록 할 수 있다.

fly()인터페이스를 Flyable 클래스를 따로 두고, 나는 오리에게서만 이 메서드를 구현하도록 하는 것의 문제점

날아가는 동작을 꽥꽥 → 끽끽 으로 바꾸기 위해서는 Duck의 서브클래스에서 fly()메서드를 구현한 모든 것을 찾아서 일일히 변경해야 한다.

상속과 인터페이스는 좋은 방법이 아니다

  • 상속

모든 Duck의 서브 클래스에서 나는 것은 아니기 때문에 Duck의 슈퍼클래스에 fly()메서드를 추가하는 것은 올바른 방법이 아니다.

또한, 나는 방법이 다 똑같은 것이 아니기 때문에 모든 서브클래스에서 한 가지 행동만 사용하도록 하는 것도 좋은 방법은 아니다.

  • 인터페이스

그렇다고해서 fly()를 인터페이스로 선언하고 각 서브 클래스에서 이를 구현하고자 하면, 인터페이스에는 구현된 코드가 없으므로 코드를 재사용할 수 없다는 문제점이 있다.

따라서, 한 가지 행동을 바꿀 때마다 fly()(나는 행동이 변했을 때) 서브 클래스에서 일일이 fly()메서드를 확인하고 변경해주어야 한다.

디자인 원칙1
1. 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
바뀌는 부분을 따로 뽑아서 캡슐화 한다. 그러면 바뀌지 않는 부분에는 영향을 주지 않고 바뀌는 부분만 고치거나 확장할 수 있다.

바뀌는 부분과 그렇지 않은 부분 분리하기**

Duck 슈퍼클래스에서 fly(), quack()는 변화하는 부분이므로 Duck에서 끄집어낸다.
이후 각 행동을 나타낼 클래스 집합을 만든다.
클래스 집합에는 다양한 메서드 구현들이 들어간다.

오리의 행동을 디자인하는 방법

디자인 원칙2
구현보다는 인터페이스에 맞춰서 프로그래밍 한다.

행동은 인터페이스(FlyBehavior)로 표현하고, 이 인터페이스를 이용해서 행동을 구현한다.
이제 fly()와 관련된 행동은 Duck 클래스에서 구현하지 않는다.

Duck 서브 클래스는 인터페이스(FlyBehavior)에만 의존하기 때문에 실제 행동 구현은 Duck 슈퍼 클래스에 국한되지 않는다.

오리의 행동 구현하기

날 수 있는 클래스는 무조건 FlyBehavior 인터페이스를 구현합니다.
꽥꽥 거리는 것과 관련된 행동도 마찬가지입니다. 반드시 구현해야만 하는 quack() 메소드가 들어있는 QuackBehavior 인터페이스가 있습니다.

변화하는 부분을 따로 인터페이스로 꺼내서, 해당 인터페이스를 구현한 클래스를 따로 만든다.

이런 식으로 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있다. 그런 행동이 더 이상 Duck 클래스 안에 숨겨져 있지 않기 때문이다.

그리고 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck 클래스를 전혀 건드리지 않고도 새로운 행동을 추가할 수 있다.

오리 행동 통합하기

Duck클래스는 FlyBehavior(변화하는 부분을 분리한 인터페이스 클래스)만 알고 있다는 것이다.

Duck 클래스에서 fly 행동을 직접 처리하는 대신, FlyBehavior에 참조되는 객체에 그 행동을 위임한다,

구체적으로 어떤 클래스를 사용하고 있는지는 모른다.

그래서 각 오리 객체는 실행시에 FlyBehavior 인터페이스 변수에 구체적인 클래스를 다형적으로 설정한다.

디자인 패턴3
상속보다는 구성을 활용한다.
두 클래스를 합치는 것을 구성이라고 한다.
변경되는 부분을 별도의 클래스 집합으로 캡슐화한다.
(구체적인 알고리즘 군을 인터페이스로 묶는다.)

구성 요소를 사용하는 객체(Duck)에서 행동을 부여받아 실행 시에 행동을 바꿀 수 있다.

코드로 보기

package strategyPattern;

public abstract class Duck {
    
    public FlyBehavior flyBehavior; //행동 변수는 행도 인스턴스 형식으로 선언한다.
    public QuackBehavior quackBehavior; //실행 시 특정 행동의 레퍼런스가 저장된다.

    public Duck() {}

    public abstract void display();

    public void performFly() {
        flyBehavior.fly(); // 나는 행동을 직접 처리하는 것 대신 flyBehavior로 참조되는 객체에 행동을 위임한다.
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void swim() {
    	System.out.printLn("모든 오리는 물에 뜹니다. 가짜오리도 뜨죠.");
    }

}
public interface FlyBehavior {
    void fly();
}
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("날고 있어요!");
    }
}

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("저는 못 날아요!");
    }
}
public interface QueakBehavior {
    void quack();
}
public class Quack implements QueakBehavior {
    @Override
    public void quack() {
        System.out.println("꽥꽥!!");
    }
}

public class MuteQuack implements QueakBehavior {
    @Override
    public void quack() {
        System.out.println("조용");
    }
}

public class Squeak implements QueakBehavior {
    @Override
    public void quack() {
        System.out.println("삑");
    }
}
public class MallardDuck extends Duck {

    public MallardDuck() {
        flyBehavior = new FlyWithWings(); //FlyWithWings객체가 할당되었다.
        quackBehavior = new Quack(); //Quack 객체가 할당되었다.
    }

    @Override
    public void display() {
        System.out.println("저는 물오리 입니다.");
    }
}
public class DuckApplication {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        mallard.performFly();
        mallard.performQuack();
    }
}


// console
날고 있어요!.!!

정리

전략 패턴이란, 비슷한 동작을 하지만 다르게 구현되어 있는 행위(전략)들을 공통의 인터페이스를 구현하는 각각의 클래스로 구현하고, 동적으로 바꿀 수 있도록 하는 패턴이다. 전략 패턴으로 구현된 코드는 직접 행위에 대한 코드를 수정할 필요 없이 전략만 변경하여 유연하게 확장할 수 있게 된다.

0개의 댓글