디자인패턴 1) 전략 패턴
CHAPTER 01. 디자인 패턴 소개와 전략 패턴
1. 오리 시뮬레이션 게임을 만든다면
-
초기 기획단계에서는 모든 오리들은 꽥꽥 소리를 낼 수 있고 수영만 할 수 있고 겉모습만 달랐다. 그래서 객체지향 기법을 사용하여 모든 오리가 가지고 있어야 하는 공통된 기능을 정의한
Duck
이라는 슈퍼클래스를 만든 다음 이를 상속받는 서브클래스를 만들어 오리의 겉모습을 각자 다르게 구현했다. - 그런데 갑자기 나는 기능도 추가되어야 한다면 어떻게 할까?
-
슈퍼클래스인
Duck
에fly()
메서드를 추가로 구현하면 상속받고 있는 모든 서브클래스에 나는 기능을 추가할 수 있다. - 그런데 나중에 보니까 일부 서브클래스에서 문제가 발견되었다. 왜냐면 서브클래스에는 고무로 만든 장난감 오리도 있었는데 그 클래스에도 날아다니는 기능이 추가된 것이었다. 고무 오리가 날아다니는 기능은 의도하지 않은 기능이기 때문에 고쳐야 한다.
public class Duck {
quack(); // 꽥꽥
swim(); // 수영
fly(); // 날기
}
// 살아 있는 오리
public class MallardDuck extends Duck {
display();
}
// 고무 오리
public class RubberDuck extends Duck {
display();
}
- 그러면 고무 오리 클래스에서
fly()
메서드를 아무 것도 하지 않도록 오버라이드 해서 문제를 해결할 수 있을 것이다. - 하지만 만약 게임이 주기적으로 업데이트 된다면 그 때마다
fly()
메서드에 새로운 기능이 추가될 수 있고, 그러면 그 때마다fly()
를 아무 것도 하지 않게 오버라이드 한 서브클래스의fly()
메서드도 수정해 줘야 할 것이다. 생각만 해도 너무 손이 많이 간다. Flyable
이라는 인터페이스를 만들어 서브클래스에서 구현하게 하면 고무 오리가 날아다니는 문제를 해결할 수 있겠지만Flyable
을 구현하는 클래스마다 각자 구현해야 하기 때문에 중복되는 코드가 많이 생기고 만약 날아가는 동작에 수정사항이 생긴다면Flyable
을 구현하는 모든 서브클래스의 코드를 수정해 줘야 한다. 이것 역시 비효율적이다.
2. 디자인 원칙 1) 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
-
이를 효율적으로 설계하기 위해서 첫 번째 디자인 원칙인
캡슐화
를 적용한다.캡슐화
는 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리하는 것이다. 그러면 코드를 변경할 일이 생기면캡슐화
된 부분만 변경하면 돼기 때문에 다른 부분에 미치는 영향을 최소화 시킬 수 있다. -
현재 오리의 기능 중에서 꽥꽥 소리를 내는
quack()
과 나는 기능인fly()
는 서브클래스의 종류에 따라 다르게 구현되어야 하기 때문에Duck
슈퍼클래스에서quack()
과fly()
를 꺼내서 각각 행동을 나타낼 클래스 집합(set)을 새로 만들어야 한다.
3. 디자인 원칙 2) 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 나는 행동과 꽥꽥거리는 행동을 구현하는 클래스 집합은 최대한 유연하게 만들어야 한다. 그리고
Duck
의 인스턴스에 행동을 할당할 수 있어야 한다. 인스턴스가 생성된 후에 행동을 동적으로 바꿀 수 있으면 더 좋을 것이다. - 이를 효율적으로 구현하기 위해 슈퍼클래스에 모든 행동을 구현하기 보다는 특정 행동만을 목적으로 하는 클래스의 집합을 만들어 행동 인터페이스를 구현한다.
public interface FlyBehaior {
fly();
}
public class FlyWithWings implements FlyBehavior {
fly();
// 나는 방법을 구현
}
public class FlyNoWay implements FlyBehavior {
fly();
// 아무것도 하지 않음
// 날 수 없다.
}
- 이런 식으로 디자인하면 특정 행동들이
Duck
클래스에 국한되지 않고 해당 기능들이 필요한 서브클래스에 추가해서 사용할 수 있다. 상속을 쓸 때 떠안게 되는 부담을 전부 떨쳐 버리고도 재사용의 장점을 그대로 누릴 수 있다.
4. 오리 행동 통합하기
Duck
클래스에서flyBehavior
와quackBehavior
라는 인터페이스 형식의 인스턴스 변수를 추가하고 각 서브클래스에서도fly()
와quack()
을 제거한다.Duck
클래스에서fly()
와quack()
메서드를 제거한 뒤 대신performFly()
와performQuack()
이라는 메서드를 넣는다.
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void performQuack() {
// 꽥꽥거리는 행동을 직접 처리하는 대신 quackBehavior로 참조되는 객체에 그 행동을 위임한다.
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
}
- 서브클래스
public class MallardDuck extends Duck {
public MallarDuck() {
// 슈퍼클래스에서 정의된 인스턴스 변수에 실제 생성된 객체를 할당
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("저는 물오리입니다.");
}
}
- 슈퍼클래스를 상속받은 서브클래스에서 각각 필요한 인스턴스를 할당해 사용할 수 있다. 재사용성이 증대된다.
5. 디자인 원칙 3) 상속보다는 구성을 활용한다.
- 각 오리에는
FlyBehavior
와QuackBehavior
가 있으며 각각 나는 행동과 꽥꽥거리는 행동을 위임받는다. 이런 식으로 두 클래스를 합치는 것을 ‘구성(composition)
을 이용한다’라고 부른다. - 구성을 활용해서 시스템을 만들면 유연성을 크게 향상시킬 수 있다. 단순히 알고리즘군을 별도의 클래스 집합으로 캡슐화할 수 있으며 구성 요소로 사용하는 객체에서 올바른 행동 인터페이스를 구현하기만 하면 실행 시에 행동을 바꿀 수도 있다.
전략 패턴
- 지금까지 학습한 디자인 패턴을 전략 패턴(Strategy Pattern)이라고 한다. 전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해 준다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.
디자인 패턴을 알아야 하는 이유
- 개발자끼리 공통으로 아는 전문 용어를 사용하면 다른 개발자와 더 쉽게 대화할 수 있고, 패턴을 아직 모르는 사람들에게는 패턴을 배우고 싶은 생각이 들도록 자극을 줄 수 있다. 또한 자질구레한 객체 수준에서의 생각이 아닌,
패턴 수준
에서 생각할 수 있기에 아키텍처를 생각하는 수준도 끌어 올려 준다.
마지막 정리
객체지향 기초
- 추상화
- 캡슐화
- 다형성
- 상속
객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
객체지향 패턴
- 전략 패턴 : 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해 준다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.