Open j03y14 opened 6 months ago
class Duck {
quack()
swim()
display()
}
class MallarDuck extends Duck {
display()
}
class RedheadDuck extends Duck {
display()
}
이렇게 되어있었음.
그런데 새로운 요구사항이 등장.
오리가 날아야 한다.
=> 처리1. Duck 클래스에 fly 메서드만 추가하면 될듯?
class Duck {
quack()
swim()
display()
fly()
}
class MallarDuck extends Duck {
display()
}
class RedheadDuck extends Duck {
display()
}
그런데 고무 오리들도 날아다닌다. (자식 중에 일부에만 추가가 되어야 하는 상황)
class Duck {
quack()
swim()
display()
fly()
}
class MallarDuck extends Duck {
display()
}
class RedheadDuck extends Duck {
display()
}
class RubberDuck extends Duck {
display()
fly() {
return; // 아무것도 하지 않도록 오버라이드
}
}
이게 맞나..?
=> 처리2. Flyable, Quackable 인터페이스를 만들어서 처리한다.
interface Flyable {
fly(): void;
}
interface Quackable {
quack(): void;
}
class Duck {
quack() {}
swim() {}
display() {}
fly() {}
}
class RedheadDuck extends Duck implements Flyable, Quackable {
display() {}
fly() {}
quack() {}
}
class MallarDuck extends Duck implements Flyable, Quackable {
display() {}
fly() {}
quack() {}
}
class RubberDuck extends Duck implements Quackable {
quack() {}
}
class DecoyDuck extends Duck {
}
이러면 코드 중복이 발생한다.
❗ 디자인 원칙1: 어플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.
fly, quack을 제외하고는 바뀌지 않고 있다.
변화하는 부분을 분리해서 나는 것, 꽥꽥거리는 것을 나타내는 클래스를 만들어야 한다.
❗ 디자인 원칙2: 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
각 행동을 인터페이스로 표현하고, 인터페이스를 구현한 클래스에서 행동을 구현한다. Duck 클래스에서 행동을 구현한 방법은 행동을 변경할 여지가 없었지만 이제 행동을 변경할 여지가 있다.
interface FlyBehavior7 {
fly(): void;
}
class FlyWithWings implements FlyBehavior7 {
fly() {}
}
class FlyNoWay implements FlyBehavior7 {
fly() {}
}
interface QuackBehavior7 {
quack(): void;
}
class Quack implements QuackBehavior7 {
quack() {}
}
class Squeak implements QuackBehavior7 {
quack() {}
}
class MuteQuack implements QuackBehavior7 {
quack() {}
}
abstract class Duck6 {
flyBehavior: FlyBehavior7;
quackBehavior: QuackBehavior7;
swim() {}
display() {}
// ? 책에서는 performQuack으로 되어있는데 왜 네이밍을 바꿔서 이렇게 했을까?
quack() {
this.quackBehavior.quack();
}
fly() {
this.flyBehavior.fly();
}
}
class RedheadDuck6 extends Duck6 {
constructor() {
super();
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new Quack();
}
display() {}
}
class MallardDuck6 extends Duck6 {
constructor() {
super();
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new Quack();
}
display() {}
}
class RubberDuck6 extends Duck6 {
constructor() {
super();
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new Squeak();
}
}
class DecoyDuck6 extends Duck6 {
constructor() {
super();
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new MuteQuack();
}
}
동적으로 행동을 변경하고 싶다.
// 문제: 동적으로 행동을 변경하고 싶다.
// 대응: setter를 통해 행동을 변경할 수 있도록 한다.
interface FlyBehavior7 {
fly(): void;
}
class FlyWithWings7 implements FlyBehavior7 {
fly() {}
}
class FlyNoWay7 implements FlyBehavior7 {
fly() {}
}
interface QuackBehavior7 {
quack(): void;
}
class Quack7 implements QuackBehavior7 {
quack() {}
}
class Squeak7 implements QuackBehavior7 {
quack() {}
}
class MuteQuack7 implements QuackBehavior7 {
quack() {}
}
abstract class Duck7 {
flyBehavior: FlyBehavior7;
quackBehavior: QuackBehavior7;
swim() {}
display() {}
setFlyBehavior(fb: FlyBehavior7) {
this.flyBehavior = fb;
}
setQuackBehavior(qb: QuackBehavior7) {
this.quackBehavior = qb;
}
// ? 책에서는 performQuack으로 되어있는데 왜 네이밍을 바꿔서 이렇게 했을까?
quack() {
this.quackBehavior.quack();
}
fly() {
this.flyBehavior.fly();
}
}
class RedheadDuck7 extends Duck7 {
constructor() {
super();
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new Quack();
}
display() {}
}
class MallardDuck7 extends Duck7 {
constructor() {
super();
this.flyBehavior = new FlyWithWings();
this.quackBehavior = new Quack();
}
display() {}
}
class RubberDuck7 extends Duck7 {
constructor() {
super();
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new Squeak();
}
}
class DecoyDuck7 extends Duck7 {
constructor() {
super();
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new MuteQuack();
}
}
function main() {
const mallard = new MallardDuck7();
mallard.setFlyBehavior(new FlyWithWings());
mallard.setQuackBehavior(new Quack());
mallard.fly();
mallard.quack();
}
이 때 행동을 알고리즘군으로 생각하면 오리가 하는 행동이 아니라 세금 계산 방식을 구현하는 클래스 등 다른 경우에도 사용할 수 있다는걸 알 수 있다.
Duck과 FlyBehaviour, QuackBehavior는 "A에는 B가 있다"의 관계이다. Duck은 각 행동을 FlyBehaviour, QuackBehaviour에 위임한다.
이런식으로 클래스를 합치는 것을 composition을 사용한다고 한다.
❗디자인 원칙3: 상속보다는 구성을 사용한다.
알고리즘군을 정의하고 캡슐화 해서 각각의 알고리즘군을 수정해서 쓸 수 있게 한다. 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.
참고
Duck의 행동을 상속할 때 생길 수 있는 단점?
QuackDuck과 FlyDuck이라고 Duck을 상속해서 만들면 문제점이 뭘까?
class Duck {
quack()
swim()
display()
fly()
}
class MallarDuck extends Duck {
display()
}
class RedheadDuck extends Duck {
display()
}
-> fly가 아무것도 하지않도록 오버라이드 해서 해결?
class RubberDuck {
quack() {}
display() {}
fly() {
// do nothing
}
}
-> 단점:
모든 서브클래스에 날거나 꽥꽥 거리는 기능이 있어야하므로, 상속이 올바른 방법이 아니다. 애플리케이션의 달리지는 부분을 찾아내고, 달라지는 부분과 그렇지 않은 부분을 분리해야한다.
이 경우, 행동을 별도의 클래스에서 구현합니다. 행동을 Duck 클래스나 그 서브클래스에서 정의하지 않고, 메소드를 써서 다른 클래스에 위임하자
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() { }
public abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
}
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() { ... }
}
Duck 클래스에 set메소드 추가해서 사용하면 가능하빈다!
오리의 행동을 알고리즘 군으로 생각한다.(날기 -> 행동이 아니라 고도나 속도 등의 정보를 포함하는 객체가 될 수 있음) 두 클래스를 함께 사용하는 것을 구성(composition)이라고 합니다.
전략 패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해줍니다. 전략 패턴을 통해 클라이언트로 부터 알고리즘을 분리해서 독립적으로 사용할 수 있습니다.
@kkirico
디자인패턴은 새로 파지말고 여기 이어서 하자. 라벨 붙여놨음.