[디자인 패턴] 1. 전략 패턴
2022. 8. 22. 14:08ㆍCS/디자인 패턴
상속의 문제점
Duck 클래스
-----------
- quack()
- swim()
- fly()
오리들이 공통적으로 울음소리, 수영, 날기를 할 수 있다고 생각해 Duck 상위 클래스를 만들고 하위 클래스에서 Duck 클래스를 상속받아 구현한다고 가정하자.
그렇지만 나무오리라면 날 수도 소리낼 수도 없다. 나무오리라면 아래와 같이 오버라이드 해야 한다.
DecoyDuck
-----------
quack() {
// 아무것도 하지 않도록 오버라이드
}
display() {
// 가짜오리
}
fly() {
// 아무것도 하지 않도록 오버라이드
}
이렇게 상속하는 상황에는 문제점이 존재한다. 상속은 아래와 같은 단점을 가진다.
- 서브클래스에서 코드가 중복
- 실행 시 특징을 바꾸기 힘들다.
- 모든 오리의 행동을 알기 힘들다.
- 코드를 변경했을 때 다른 오리들에게 원치 않은 영향을 끼칠 수 있다.
Duck 클래스에 전략 패턴 적용
1. 바뀌는 부분과 안 바뀌는 부분을 정리
Duck 클래스에서 바뀌는 부분은 quack 과 fly 이다.
왜냐하면 오리 종류에 따라 달라진다. (날 수 없을수도 있고, 소리를 안 낼 수도 있다.)
2. 인터페이스에 맞춰서 프로그래밍
나는 행동과 꽥꽥거리는 행동을 어떻게 디자인해야 할까?
각 행동을 인터페이스로 표현하고 이런 인터페이스를 사용해서 행동을 구현하자
나는 행동과 꽥꽥거리는 행동은 이제 Duck 클래스에서 구현하지 않고, 특정 행동만을 목적으로 하는 클래스의 집합을 만든다.
나는 행동과 꽥꽥거리는 행동 등을 구현하는 클래스 예시이다.
interface FlyBehavior {
fly();
}
class FlyWithWings implements FlyBehavior {
fly() {
// 나는 방법 구현
}
}
class FlyNoWay implements FlyBehavior {
fly() {
// 아무것도 하지 않음, 날 수 있다.
}
}
interface QuackBehavior {
quack();
}
class Quack implements QuackBehavior {
quack() {
// 꽥꽥
}
}
class Squack implements QuackBehavior {
quack() {
// 삑삑
}
}
class MuteQuack implements QuackBehavior {
quack() {
// 아무것도 하지 않음
}
}
이런식의 디자인의 장점은 다음과 같다.
- 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있다. 그런 행동이 더 이상 Duck 클래스 안에 숨겨져 있지 않기 때문
- 기존의 행동 클래스를 수정하거나 날아다니는 행동을 사용하는 Duck 클래스를 건드리지 않고도 새로운 행동을 추가할 수 있다.
- 따라서 상속을 쓸 때 떠안게 되는 부담을 전부 떨쳐 버리고 재사용의 장점을 그대로 누릴 수 있다.
3. Duck 클래스
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
- 나는 행동과 꽥꽥거리는 행동을 Duck 클래스에서 정의한 메서드를 써서 구현하지 않고 다른 클래스에 위임한다
- 위임하는 방법은 Duck 클래스에 flyBehavior, quackBehavior 라는 인터페이스 형식의 인스턴스 변수를 추가한다.
- 모든 Duck 클래스에는 FlyBehavior, QuackBehavior 인터페이스를 구현하는 것의 레퍼런스가 있다.
- 행동을 직접 처리하는 대신에 flyBehavior, quackBehavior로 참조되는 객체에 그 행동을 위임한다.
4. 하위 클래스
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");
}
}
- MallardDuck은 Duck 클래스에서 quackBehavior와 flyBehavior 인스턴스 변수를 상속받는다
- MallardDuck이 꽥꽥거리는 행동을 처리할 때는 Quack 클래스를 사용하므로 performQuack()이 호출되면 꽥꽥거리는 행동은 Quack 객체에 위임된다.
5. 테스트
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
이런 식으로 나는 행동과 꽥꽥거리는 행동을 캡슐화된 알고리즘으로 구현했다.
즉 전략패턴 의미는 다음과 같다.
💡 전략패턴이란?
전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해 줍니다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.
적용된 디자인 원칙
- 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분을 분리한다.
- 바뀌는 부분을 따로 뽑아서 캡슐화한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다는 뜻이다. 즉 시스템의 ‘유연성’이 좋아진다.
- 이 디자인 원칙은 모든 디자인 패턴의 기반을 이루는 원칙이다. 모든 패턴은 ‘시스템의 일부분을 다른 부분과 독립적으로 변화시킬 수 있는’ 방법을 제공하기 때문이다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍 한다.
- 상속보다는 구성(composition)을 활용한다.
- 두 클래스를 합치는 것을 ‘구성을 이용한다’ 라고 부른다.
- 오리 클래스에서 행동을 상속받는 대신, 올바른 행동 객체로 구성되어 행동을 부여받는다.
- 알고리즘군을 별도의 클래스 집합으로 캡슐화 시킬 수 있으며 구성 요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만 하면 실행 시에 행동을 바꿀 수도 있다.
'CS > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 2. 옵저버 패턴 (0) | 2022.08.23 |
---|