객체의 행동을 자유롭게 바꿔 끼울 수 있는 패턴
상속(Inheritance)보단 조립(Composition)을 활용하라는 객체지향 원칙을 따른다.
quack(), swim() 메서드를 가진 "오리" 클래스가 있다.
오리들이 날아다닐 수 있도록 하고 싶다.
오리 클래스에 fly() 메서드를 추가했다.
그랬더니 날면 안 되는 고무오리까지 날아다니게 됐다.
상속으로 코드를 재사용할 수 있을 거 같았지만 별 도움이 안 된다.
날면 안 되는 오리에 날지 못하도록 fly() 메서드를 오버라이드 해보려니
오리마다 fly() 메서드가 아무것도 안 하도록 오버라이드를 해야 돼서 코드 칠 게 너무 많다.
fly() 메서드를 가진 Flayable 인터페이스와, quack() 메서드를 가진 Quackable 인터페이스를 만들었다.
인터페이스를 사용해서 자식 오리 클래스들이 자신들이 하는 행동만 구현해서 사용할 수 있도록 했다.
오리 종류 100가지나 된다면, 모든 오리 서브 클래스의 메서드를 오버라이드 하기란 쉽지 않다.
애플리케이션에서 달리지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다.
다시 말하면,
바뀌는 부분은 따로 뽑아서 캡슐화시킨다. 그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다.
fly()와 quack()은 오리 클래스에서 오리마다 달라진다.
두 메서드를 분리해서 각 행동을 나타내는 새로운 집합을 만든다.
구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
FlyBehavior와 QuackBehavior라는 두 인터페이스와 구체적인 행동을 구현하는 클래스들을 만든다.
이런 식으로 디자인하면 다른 타입의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있다.
또한, 기존의 오리 클래스를 건드리지 않고도 새로운 행동을 추가할 수 있다.
오리의 행동을 오리 클래스에서 구현하지 않고 다른 클래스에 위임한다.
이런 식으로
class 오리 {
// 모든 오리는 QuackBehavior 인터페이스를
// 구현하는 것에 대한 레퍼런스를 가진다.
quackBehavior: QuackBehavior;
performQuack(): void {
// 꽥꽥거리는 행동을 직접 처리하는 대신,
// quackBehavior로 참조되는 객체에 그 행동을 위임한다.
quackBehavior.quack();
}
}
오리 클래스에 아래 메서드를 추가하고 필요에 따라 호출해서 행동을 바꾸면 된다.
setFlyBehavior(fb: FlyBehavior) {
this.flyBehavior = fb;
}
setQuackBehavior(qb: QuackBehavior) {
this.quackBehavior = qb;
}
abstract class 오리 {
// 행동 인터페이스에 대한 레퍼런스
flyBehavior: FlyBehavior;
quackBehavior: QuackBehavior;
constructor() {}
abstract display(): void;
// 클래스에 행동을 위임
performFly(): void {
this.flyBehavior.fly();
}
performQuack(): void {
this.quackBehavior.quack();
}
setFlyBehavior(fb: FlyBehavior) {
this.flyBehavior = fb;
}
setQuackBehavior(qb: QuackBehavior) {
this.quackBehavior = qb;
}
swim(): void {
console.log("모든 오리는 물에 뜬다.");
}
}
// 나는 행동
interface FlyBehavior {
fly(): void;
}
class FlyWithWings implements FlyBehavior {
fly(): void {
console.log("날고 있어요");
}
}
class FlyNoWay implements FlyBehavior {
fly(): void {
console.log("못 날아요");
}
}
// 꽥꽥거리는 행동
interface QuackBehavior {
quack(): void;
}
class Quack implements QuackBehavior {
quack(): void {
console.log("꽥");
}
}
class MuteQuack implements QuackBehavior {
quack(): void {
console.log("(조용..)");
}
}
class Squeak implements QuackBehavior {
quack(): void {
console.log("삑");
}
}
class 청둥오리 extends 오리 {
constructor() {
super();
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new Quack();
}
display(): void {
console.log("진짜 청둥오리");
}
}
const 청둥 = new 청둥오리();
청둥.performFly(); // 날고 있어요
청둥.performQuack(); // 꽥
- 행동 메서드를 호출할 때 실행에 필요한 의존성(변수, 다른 메서드)은 어떻게 구하는가?
- Head First Design Patterns