기타/디자인패턴
[Design Pattern] Strategy Pattern
[dev] hiro
2024. 5. 27. 08:47
해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)
SimUduck
- 모든 오리가 할 수 있는 동작 Duck 클래스에서 정의
- quack()
- swim()
- display()
- 모든 오리의 동작이 다르므로 추상 메소드에서 정의
- MallardDuck 클래스와 RedHeadDuck 클래스가 Duck 클래스를 상속받아서 display override
- 이후에 모든 오리에게 날 수 있는 동작을 정의하고 싶음
- Duck 클래스에서 fly() 메소드 추가로 정의로 해결 가능
문제발생
- fly() 메소드는 일부 Duck 클래스를 상속한 서브 클래스에만 되어야했는데 모든 서브 클래스에 적용이 다 되어버림
해결책
1. 인터페이스 설계
- 모든 오리가 날지 못함 + 모든 오리가 꽥꽥거리면서 울지 않음
- Flyable, Quackable 이라는 인터페이스 설계
2. 캡슐화
💡 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리.
- 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화
- 코드를 변경하는 과정에서 의도치 않게 발생하는 일을 줄이면서 시스템의 유연성을 향상.
- 구현보다는 인터페이스에 맞춰 프로그래밍한다.
- 각 행동은 인터페이스로 표현(FlyBehavior, QuackBehavior…)
- 메소드는 Duck 클래스에서 구현X. 인터페이스에서 먼저 정의
💡 구현보다는 인터페이스에 맞춰 프로그래밍한다.
오리행동 통합하기
나는 행동과 꽥꽥거리는 행동을 Duck 클래스에서 정의한 메소드를 써서 구현하지 않고 다른 클래스로 위임하는 행동.
- Duck 클래스에 FlyBehavior와 QuackBehavior을 인스턴스 변수로 저장.
- Duck 클래스는 인터페이스에서 구현한 fly()와 quack() 메소드를 실행하기 위한 메소드 정의(performFly(), performQuack())
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
// 모든 오리의 동작을 여기서 정의
void performQuack() {
quackBehavior.quack();
}
void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
void performFly() {
flyBehavior.fly();
}
void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
void swim() {
System.out.println("헤엄!");
}
// 오리에 따른 description
abstract void display();
}
/*
* 오리 날기 정의
* 인터페이스로 정의 후 각각의 날개의 방법대로 정의.
*/
public interface FlyBehavior {
void fly();
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("i can't fly!");
}
}
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("i can fly!");
}
}
/*
* 오리 울음소리 정의
* 인터페이스로 정의 후 각각의 소리마다 class로 정의
*/
public interface QuackBehavior {
void quack();
}
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("꽥꽥!");
}
}
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("삑삑!");
}
}
Example
public class RedheadDuck extends Duck {
public RedheadDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Squeak();
}
@Override
void display() {
System.out.println("RedheadDuck!");
}
}
- RedheadDuck 클래스는 Duck 클래스를 상속.
- 상속자에서 날기 동작과 울음소리를 정의.
- 날기 동작과 울음소리는 인터페이스로 정의되어 있는 것에 클래스로 할당.
- display()는 Duck 클래스에서 추상메소드로 정의하였기 때문에 클래스에서 정의.
💡 특정 구현에 맞춰서 프로그래밍하면 안됨.
하지만 new FlyWithWings() 처럼, 특정 객체에 구현되어 있는 구상 인스턴스를 만들었음. ⇒ 잘못되었지만 1장이라 이처럼 표현
동적으로 행동 지정하기
- Duck 클래스에 Setter method 정의(setQuackBehavior(), setFlyBehavior())
두 클래스를 합치는 방법
💡 상속(as-is)보다는 구성(has-is)을 활용
- A에는 B가 있다. ⇒ Duck 클래스에 FlyBehavior와 QuackBehavior가 있어서 Duck 클래스에 나는 행동과 꽥꽥거리는 행동을 위임. ⇒ 구성
전략패턴
알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해주는 패턴
클라이언트로부터 알고리즘을 분리해서 독립적으로 분리.
요약 :
변화가 생길 기능(행동:behavior)을 예상하여 이를 캡슐화(및 추상화) 한다.
캡슐화 후 각 기능별로 구현해두고(ex: fly 중 noWay, WithWing, Rocket 등)
각 객체별로 사용할 기능을 (쉽게) 교환 받는다.
각 기능들이 추상화되어 있어 재사용성이 좋고, 수정이 용이하다.
기억 추적 :
서브클래스는 단순히 super 클래스를 상속받는식의 구현이 아닌,
interface, abstract 등을 이용하여 기능의 동작만 받음.
각 기능은 인터페이스를 구현하여 미리 정의해두고 모델은 이를 조합하여 기능을 만들어가는 방식.