학습/디자인 패턴

1. 전략 패턴(Strategy Pattern)

찌김이 2023. 4. 16. 16:20
728x90
반응형

책에서는 예제로 오리 시뮬레이션 게임을 만들 때 모든 오리가 공통적으로 가지고 있을 법한 행동을 담은 Duck 클래스를 만들어 오리마다 Duck 클래스를 상속받아 구현하였습니다. 

 

public class Duck {
    public void quack() {
        System.out.println("Quack");
    }

    public void swim() {
        System.out.println("Swim");
    }

    public void display() {
        System.out.println("Display");
    }
}

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

위 코드에서는 Duck 클래스를 정의하고, MallardDuck 클래스를 Duck 클래스를 상속받아 구현합니다. MallardDuck 클래스에서는 display() 메서드를 재정의하여 Mallard 오리의 특성에 맞게 출력하도록 합니다.

하지만, 만약 다른 종류의 오리 클래스도 추가된다면, 각각의 오리 종류에 맞게 quack(), swim(), display() 메서드를 계속해서 재정의해주어야 합니다. 

이는 코드의 중복을 유발하며, 유지보수성을 떨어뜨리게 됩니다.

 

이러한 문제를 해결하기 위해 전략 패턴을 사용할 수 있습니다. 전략 패턴은 객체 간의 관계를 동적으로 구성하는 패턴으로, 상속을 사용하지 않습니다. 각각의 오리 객체는 fly(), quack()와 같은 행동을 각각의 전략 객체에 위임합니다.

 

다음은 전략 패턴을 사용하여 Mallard 오리 객체를 구현한 예제 코드입니다.

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {
    }

    public abstract void display();

    public void performFly() {
        flyBehavior.fly();
    }

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

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }
}

public interface FlyBehavior {
    void fly();
}

public interface QuackBehavior {
    void quack();
}

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

    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

public class Quack implements QuackBehavior {
    public void quack()
        System.out.println("Quack");
    }
}

public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("I'm flying!!");
    }
}

위 코드에서는 Duck 클래스를 추상 클래스로 정의하고, FlyBehavior와 QuackBehavior 인터페이스를 정의합니다. MallardDuck 클래스는 Duck 클래스를 상속받아 구현하며, flyBehavior와 quackBehavior를 각각 FlyWithWings와 Quack으로 설정합니다. display() 메서드는 Mallard 오리의 특성에 맞게 재정의됩니다.

 

Quack과 FlyWithWings 클래스는 각각 QuackBehavior와 FlyBehavior를 구현한 구체적인 클래스입니다. 이를 통해 각각의 행동을 구현하고, Duck 클래스에서는 이를 위임하여 유연하게 동작하도록 합니다.

 

전략 패턴을 사용하면, 새로운 오리 종류가 추가되더라도 새로운 전략 객체를 만들어 flyBehavior와 quackBehavior를 설정하면 되기 때문에 기존의 코드를 수정할 필요가 없습니다. 이는 코드의 유지보수성을 높이고, 확장성을 높일 수 있습니다.

 

전략 패턴(Strategy Pattern)은 코드의 재사용성과 유지보수성을 높일 수 있지만, 사용하는 클래스의 개수가 많아질 수 있다는 단점이 있습니다. 또한, 전략 인터페이스에 추가되는 메서드가 많아지면, 전략 클래스들을 수정해야 하는 문제가 있습니다. 이는 전략 패턴의 유연성 때문에 발생하는 문제이며, 적절한 상황에서 사용할 필요가 있습니다.

또한, 전략 객체를 구현하는 비용이 추가될 수 있다는 단점도 있습니다. 전략 객체를 구현하는 비용은 애플리케이션에서 사용하는 전략의 수와 관련이 있으며, 전략 패턴을 사용할 때에는 이러한 비용을 고려해야 합니다.

마지막으로, 전략 패턴을 적용할 때에는 클래스 간의 상호작용이 복잡해질 수 있다는 단점도 있습니다. 전략 패턴은 객체 간의 결합도를 낮추는 효과가 있지만, 이로 인해 코드의 복잡도가 증가할 수 있습니다. 따라서, 전략 패턴을 적용할 때에는 코드의 복잡도와 유지보수성을 고려하여 적절하게 사용해야 합니다.

 

디자인 원칙

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.

  • 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화
    • ex) 나는 행동, 우는 행동
  • 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있음

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

  • 상위 형식에 맞춰서 프로그래밍한다라는 말과 동일
  • 변수를 선언할 때 보통 추상 클래스나 인터페이스 같은 상위 형식으로 선언
    • 객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 넣을 수 있기 때문
    • 변수를 선언하는 클래스에서 실제 객체의 형식을 몰라도 됨

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

  • A 에는 B 가 있다
  • ex) 오리 클래스는 행동을 상속받는 대신, 올바른 행동 객체로 구성되어 행동을 부여 받음
728x90
반응형