전략패턴

임종혁·2024년 10월 3일
public class Duck {

    public void quack(){
        System.out.println("꽥");
    }
    public void swim(){
        System.out.println("수영");
    }

    public void display(){
        System.out.println("디스플레이");
    }
}

다음과 같은 오리 클래스가 있고
그에 상속받아 사용하는 클레스가 있다

public class MallardDuck extends Duck{
    @Override
    public void display() {
        System.out.println("Mallard");
    }

}
public class RedheadDuck extends Duck{

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

여기 오리에 이제 나는 기능을 추가할 것이다

public class Duck {
    
    public void quack(){
        System.out.println("꽥");
    }
    public void swim(){
        System.out.println("수영");
    }

    public void display(){
        System.out.println("디스플레이");
    }
    
    public void fly(){
        System.out.println("날다"); // 나는 기능 추가 
    }
}

헌데 문제가 생겼다. 날지 못하는 오리들도 날아다니는 문제가 생겼다.
ex) 고무 오리

public class RubberDuck extends Duck{
 // 나는 문제 발생   
}

그럼 오버라이드를해 못날게 하자니 날지 못하는 관련 오리들은 전체 바꿔야하는 중복코드가 발생하고
그렇다고 상위클래스에서 fly() 를 빼자니 나는 오리들에게서 중복코드가 발생한다는 문제가 생겼다.

인터페이스 설계하기

여기서 우리는 이제 인터페이스라는 것을 생각할 수있다

public interface Duck {
    void swim();
    void display();

}


public interface Fly {
    void fly();
}

다음과 같이 변하지 않는 오리 와 변하는 나는 기능을 분리해 인터페이스를 만들시

public class MallardDuck implements Fly, Duck{
    @Override
    public void swim() {

    }

    @Override
    public void display() {

    }

    @Override
    public void fly() {

    }
}
public class RubberDuck implements Duck{
    @Override
    public void swim() {
        
    }

    @Override
    public void display() {

    }
}

자신의 기능의 맞는 인터페이스를 사용하면 된다

허나 이 경우도 같은 코드 swim() 같은 코드를 재사용하지 않아 override 시 중복된다는 점 에서 문제가 된다
이경우 만약 swim() 방식을 변화하면 swim 을 사용하는 모든 클래스를 찾아 변경해야한다.

그럼 어떻게 해야한단 말인가??

우선 디자인 원칙부터 보자

디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리한다
달라지는 부분을 찾아 나머지 코드에 영향을 주지 않도록 캡슐화 하자
그러면 코드를 변경하는 과정에서 의도치 않게 발생하는 일을 줄이면서 시스템의 유연성을 향상시킬수 있다.

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

그럼 바뀌는 부분은 무엇이 있을까

  • quack()
  • fly()

바뀌지 않는 부분

  • swim()

디자인 원칙 2

구현 보다는 인터페이스 맞춰서 프로그래밍 한다

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("날지 못해요 ");
    }
}

못나는 기능을 구현하자
이런식으로 하면 나는 기능을 재사용할 수 있다.

또한 duck 클래스를 건드리지 않고 새로운 행동을 추가할 수 있다.

public abstract class Duck {
    QuackBehavior quackBehavior;
    FlyBehavior flyBehavior;
    
    public void performQuack(){
        quackBehavior.quack();;
    }
}

해당 위 클래스를 사용하는 오리가 있을시

 quackBehavior.quack();;

다음으로 인해 참조되는 객체에 그 행동을 위임한다

public class MallardDuck extends  Duck {
    public MallardDuck() {
         quackBehavior = new Quack();
         flyBehavior = new FlyWithWings();

    }

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

다음과 같은 서브 클래스에서 행동을 위임받아 사용한다

실행시

public class DuckSimulator {
    public static void main(String[] args){
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();

    }
}

즉 Duck의 관점으로 보면 Duck 은 PerformFly() 를 사용했지만 나는지 안나는지는 관심이 없다
해당 구현은 FlyBehvior 속 FlyNoWay를 구현한 MallardDuck 서브클래스 에게 책임이 있다.

즉 MallardDuck 이 갑자기 못난다면 이제 MallardDuck 만 수정하면 된다

동적으로 사용하기

 public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

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

다음과 같이 duck 클래스에 set을 넣고

public class ModelDuck extends Duck{

    public ModelDuck(){
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }
    @Override
    public void display() {
        System.out.println("모형 오리입니다.");
    }
}

ModelDuck 클래스를 추가하겠다 이 클래스는 처음에는 날지 못하는 상태이다.

Duck model = new ModelDuck();
        model.performFly();

        model.setFlyBehavior(new FlyWithWings());
        model.performFly();

다음과 같이 set으로 FlyBehavior 값 변경시

중간에 값이 변하는 형식을 볼수 있다.

캡슐화 행동 살펴보기

  • 오리들은 모두 Duck 을 확장해서 만든다
  • 나는 행동들은 FlyBehabior를
  • 꽥꽥 거리는 행동은 QuackBehavior를 구현해서 만든다

A에는 B 가 있다 관계

각 오리에는 FlyBehavior 와 QuackBehavior 가 있으며 각 나는 행동과 꽥꽥거리는 행동을 위임 받는다
이런식으로 두 클래스를 합치는 것을 구성을 이용한다라고 부른다

각 오리 클래스들은 행동을 상속받는 대신 올바른 행동 객체로 구성되어 행동을 부여받는다

디자인 원칙

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

구성을 활요해 시스템을 만들면 유연성을 크게 향상 시킬수 있다.

0개의 댓글