동일 계열의 알고리즘(행위)들을 정의하고 캡슐화하여 유지보수를 용이하게 한다.
전략 패턴 구현 시
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의 힘을 이용하여 해결하자. 상속을 이용할 수도 있겠지만 상속에는 다중 상속 문제와 상속 구조를 파악하기 어려울 수 있는 문제가 있으므로 인터페이스를 이용하는 것이 좋다.