[헤드퍼스트 디자인패턴] 01. 전략패턴
오리 시뮬레이션 게임, SinuUduck
1. 메타몽은 오리 시뮬레이션 게임을 만드는 회사를 다니고 있습니다. 이 게임에는 매우 다양한 오리가 등장하여, 메타몽은 표준 객체지향 기법을 사용하여 Duck 이라는 슈퍼 클래스를 만든 다음, 그 클래스를 확장해서 서로 다른 종류의 오리를 만들었습니다.
그러나 회사 임원진은, 오리에게 특별한 기능이 있어야 한다며 오리를 날게 해달라고 했고, 메타몽은 Duck 클래스에 fly() 기능을 추가하였습니다.
이럴수가! 고무 오리는 날수 없는데 고무 오리가 날아다니는 오류가 발생했습니다.
그래서 quack() 처럼 fly()도 오버라이드 처리를 해주었습니다. 가짜 오리 클래스도 새로 추가해주었습니다.
2. 메타몽은 방금 임원진이 앞으로 6개월마다 제품을 업데이트 하기로 결정했다는 쪽지를 받았습니다. 앞의로의 규격이 계속 바뀔거라는 사실은 불 보듯 훤했기 때문에 상속을 계속 활용한다면 규격이 바뀔 때 마다 프로그램에 추가했던 Duck의 서브클래스 fly()와 quack() 메소드를 일일이 살펴보고 상황에 따라 오버라이드 해야합니다. 영원히 반복해서 말이죠. 이렇게 하고 보니, 코드를 재사용한다는 점에서 상속을 기가막히게 활용했다고 생각했는데 유지보수를 생각하니 별로 안좋아보였습니다.
그래서 메타몽은 fly()와 quack()을 슈퍼클래스에서 빼서 interface로 만들기로 했습니다.
이렇게 하니 일부 문제점은 해결할 수 있지만, 코드를 재사용하지 않으므로 코드 관리에 커다란 문제가 생깁니다. 왜냐하면 interface를 사용하는 class에서 해당 코드를 전부 구현해줘야 하기 때문이죠.
이에 따라 메타몽은 디자인 패턴 중 전략 패턴이 필요해집니다.
디자인 원칙 1
애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
바뀌는 부분은 따로 뽑아서 캡슐화 한다. 그러면 나중에는 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장 할 수 있다.
전략 패턴
1. 바뀌는 부분과 그렇지 않은 부분 분리하기
fly()와 quack()은 Duck 클래스에 있는 오리 종류에 따라 달라집니다.
fly()와 quack()을 Duck 클래스로부터 분리하려면, 2개의 메소드 모두 Duck 클래스에서 끄집어내서 각 행동을 나타낼 클래스 집합을 새로 만들어야 합니다.
디자인 원칙 2
구현보다는 인터페이스에 맞춰서 프로그래밍 한다.
이제부터는 각 행동을 인터페이스로 표현하고, 이 인터페이스를 사용해서 행동을 구현합니다.
이런 식으로 디자인 하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용 할 수 있습니다. 그리고 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck 클래스를 건드리지 않고도 새로운 행동을 추가할 수 있습니다.
2. 오리 행동 통합하기
가장 중요한 점은 나는 행동과 꽥꽥거리는 행동을 Duck 클래스(또는 그 서브클래스)에서 정의한 메소드를 써서 구현하지 않고 다른 클래스에 위임한다는 것입니다.
이제 꽥꽥 거리고 싶을 땐 객체의 종류에는 전혀 신경 쓸 필요 없이 quack()을 실행할 줄 만 알면 됩니다.
public class 청둥오리 extends Duck {
public 청둥오리() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("저는 물오리입니다")
}
}
물론, setter method를 이용해서 구현 클래스를 수정할 수도 있습니다.
3. 두 클래스를 합치는 방법
"A에는 B가 있다" 관계를 생각해 봅시다. 각 오리에는 FlyBehavior와 QuackBehavior가 있으며, 각각 나는 행동과 꽥꽥거리는 행동을 위임받습니다. 이런식으로 두 클래스를 합치는 것을 '구성(composition)을 이용한다'라고 부릅니다. 여기에 나와 있는 오리 클래스에서는 행동을 상속받는 대신, 올바른 행동 객체로 구성되어 행동을 부여받습니다. 구성은 매우 중요한 테크닉이자 세번째 디자인 원칙이기도 합니다.
3. 오리 코드 테스트
클래스와 인터페이스 정의
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {}
public abstract void display();
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("모든 오리는 물에 뜹니다. 가짜 오리도 뜨죠");
}
}
public interface FlyBehavior {
public void fly();
}
-------------------------------------------------
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("날고 있어요!!!");
};
}
-------------------------------------------------
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("저는 못 날아요!!!");
};
}
public interface QuackBehavior {
public void fly();
}
-------------------------------------------------
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("꽥!");
};
}
-------------------------------------------------
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< 조용 ~ >> ");
};
}
-------------------------------------------------
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("삑");
};
}
코드 실행
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard = new 청둥오리();
mallard.performQuack();
mallard.performFly();
}
}
// 결과
꽥
날고있어요!!!
디자인 원칙 3
상속보다는 구성을 사용한다.
전략패턴이란?
알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해줍니다. 전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
'스터디 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[헤드퍼스트 디자인패턴] 06. 커맨드 패턴(Command Pattern) (0) | 2022.07.10 |
---|---|
[헤드퍼스트 디자인패턴] 05. 팩토리 패턴(Factory Pattern) (1) | 2022.07.03 |
[헤드퍼스트 디자인패턴] 04. 싱글턴 패턴(Singleton Pattern) (0) | 2022.06.18 |
[헤드퍼스트 디자인패턴] 03. 데코레이터 패턴(Decorator Pattern) (0) | 2022.06.18 |
[헤드퍼스트 디자인패턴] 02. 옵저버 패턴(Observer Pattern) (0) | 2022.06.12 |
댓글