동일 계열의 알고리즘(행위)들을 정의하고 캡슐화하여 유지보수를 용이하게 한다.
전략 패턴 구현 시
1. 클라이언트 클래스에서 의존성을 주입받고
2. 클라이언트 클래스에서는 인터페이스들을 이용해 알고리즘만 구현한다.
그래서 외부(스프링이라면 컨테이너)에서 입력받은 인스턴스에 따라 클라이언트 클래스에서 다른 알고리즘을 사용할 수 있다.
이런 방식은 새로운 동작이 추가되더라도 기존 구현체를 수정할 필요없이 구현체만 추가해주면 되기 때문에, 변경에는 닫혀있고 확장에는 열려있는 형태로 유지보수할 수 있다는 것을 의미한다(OCP).
또한 런타임 환경에서 실시간으로 동작을 변경할 수도 있다.
전략패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸수 있게 해 줍니다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
이 패턴을 활용하면 레고 블럭을 갈아 끼우듯 관심있는 부분에 대해서만 수정 및 테스트가 가능한 형태로 설계가 되기때문에 유지보수에 이점이 있다. 무엇보다도 전략 패턴은 많은 디자인 패턴의 원리이며, 자바 API를 비롯하여 스프링에서도 많이 사용된다.
💡 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않은 부분과 분리하는 디자인 원리.
난다, 소리를 낸다 추상화된 역할이 있는 인터페이스를 생성한다. 클라이언트 코드에서는 이 추상화된 역할만을 생각하여 개발한다.💡 상속보다는 구성을 활용하는 디자인 원리.
칼을 휘두른다활을 쏜다.물건을 훔친다.동작 배정도 변경해야한다.정액 쿠폰을 사용할지, 정률 쿠폰을 사용할지, 무료 쿠폰을 사용할지한 상품에만 적용할지, 전체 상품에 적용을 할지금액 제한 적용을 둘지, 무제한 적용일지zip, tar, rar 등)에 따라 다른 알고리즘을 적용한다.변하는 것과 변하지 않는 것을 분류한다.
소리를 내는 행위와 나는 행위가 변화 대상이다.
출처:헤드퍼스트 디자인패턴추상 클래스를 만든다. 여기서 핵심은 각 행위(또는 알고리즘)을 위임하는 것이다.
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
String name;
public Duck(){
}
//오리가 난다.
//나는 행위를 실행하도록 위임한다.
//그러므로 클라이언트에서 런타임에서 생성한 인스턴스 종류에 따라 다를 것이다.
public void performFly(){
flyBehavior.fly();
}
//오리가 소리를 낸다.
public void performQuack(){
//소리내는 행위를 실행하도록 위임한다.
quackBehavior.quack();
}
//오리가 자기소개를 한다.
public void sayMyName(){
System.out.println("============================");
System.out.println("내 이름은 "+ this.name +"입니다.");
};
}
천둥오리와 러버덕을 Duck객체 상속을 통해 만든다. 어떤 행위를 할지는 나는 행위와 소리내는 행위의 인터페이스 구현체를 주입한다.
public class MalladDuck extends Duck{
public MalladDuck(){
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new QuackLoudly();
this.name = "천둥오리";
}
}
public class RubberDuck extends Duck{
public RubberDuck(){
this.quackBehavior = new QuackLoudly();
this.flyBehavior = new FlyNoWay();
this.name = "러버덕";
}
}
행위 or 알고리즘을 인터페이스로 만들고
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
인터페이스를 구현한다.
public class QuackLoudly implements QuackBehavior{
@Override
public void quack() {
System.out.println("꽥!!!!!!");
}
}
public class MuteQuack implements QuackBehavior{
@Override
public void quack() {
System.out.println("...(아무런 소리도 나지 않는다.)");
}
}
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("난 날 수가 없어......");
}
}
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("날개를 펼쳐 하늘을 날고 있습니다.");
}
}
테스트 코드
public class DuckTest {
public static void main(String[] args){
Duck malladDuck = new MalladDuck();
malladDuck.sayMyName();
malladDuck.performQuack();
malladDuck.performFly();
Duck rubberDuck = new RubberDuck();
rubberDuck.sayMyName();
rubberDuck.performQuack();
rubberDuck.performFly();
Duck woodDuck = new WoodDuck();
woodDuck.sayMyName();
woodDuck.performQuack();
woodDuck.performFly();
}
}
낮게 난다거나 암닭 소리를 낸다 등(...)의 구현을 추가한다면 기존 구현체 클래스를 수정하지 않고 인터페이스 구현체 클래스를 추가 생성하여 간단하게 기능을 추가할 수 있다.
난다, 소리를 낸다 외에 먹이를 먹는다 등의 새로운 행위군을 추가해야한다면 인터페이스도 수정해야해하고, 인터페이스 구현체를 수정하고 각 오리의 구성에도 추가해줘야한다.
행위가 추가될 때 구현체에 일일이 추가해야 하는 것은 어쩔 수 없는 선택이다. IDE의 힘을 이용하여 해결하자. 상속을 이용할 수도 있겠지만 상속에는 다중 상속 문제와 상속 구조를 파악하기 어려울 수 있는 문제가 있으므로 인터페이스를 이용하는 것이 좋다.