해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

SimUduck


  • 모든 오리가 할 수 있는 동작 Duck 클래스에서 정의
    • quack()
    • swim()
    • display()
      • 모든 오리의 동작이 다르므로 추상 메소드에서 정의
  • MallardDuck 클래스와 RedHeadDuck 클래스가 Duck 클래스를 상속받아서 display override
  • 이후에 모든 오리에게 날 수 있는 동작을 정의하고 싶음
    • Duck 클래스에서 fly() 메소드 추가로 정의로 해결 가능

문제발생

  • fly() 메소드는 일부 Duck 클래스를 상속한 서브 클래스에만 되어야했는데 모든 서브 클래스에 적용이 다 되어버림

해결책


1. 인터페이스 설계

  • 모든 오리가 날지 못함 + 모든 오리가 꽥꽥거리면서 울지 않음
    • Flyable, Quackable 이라는 인터페이스 설계

2. 캡슐화

💡 애플리케이션에서 달라지는 부분을 찾아내고 달라지지 않는 부분과 분리.

 

  • 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화
    • 코드를 변경하는 과정에서 의도치 않게 발생하는 일을 줄이면서 시스템의 유연성을 향상.
  • 구현보다는 인터페이스에 맞춰 프로그래밍한다.
    • 각 행동은 인터페이스로 표현(FlyBehavior, QuackBehavior…)
    • 메소드는 Duck 클래스에서 구현X. 인터페이스에서 먼저 정의
💡 구현보다는 인터페이스에 맞춰 프로그래밍한다.

오리행동 통합하기


나는 행동과 꽥꽥거리는 행동을 Duck 클래스에서 정의한 메소드를 써서 구현하지 않고 다른 클래스로 위임하는 행동.

  • Duck 클래스에 FlyBehaviorQuackBehavior을 인스턴스 변수로 저장.
    • Duck 클래스는 인터페이스에서 구현한 fly()quack() 메소드를 실행하기 위한 메소드 정의(performFly(), performQuack())
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    // 모든 오리의 동작을 여기서 정의

    void performQuack() {
        quackBehavior.quack();
    }

    void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    void performFly() {
        flyBehavior.fly();
    }

    void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    void swim() {
        System.out.println("헤엄!");

    }

    // 오리에 따른 description
    abstract void display();
}

/*
 * 오리 날기 정의
 * 인터페이스로 정의 후 각각의 날개의 방법대로 정의.
 */
public interface FlyBehavior {
    void fly();
}

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("i can't fly!");
    }
}

public class FlyWithWings implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("i can fly!");
    }
}

/*
 * 오리 울음소리 정의
 * 인터페이스로 정의 후 각각의 소리마다 class로 정의
 */
public interface QuackBehavior {
    void quack();
}

public class Quack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("꽥꽥!");
    }
}

public class Squeak implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("삑삑!");
    }
}

 

Example

public class RedheadDuck extends Duck {

    public RedheadDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Squeak();
    }

    @Override
    void display() {
        System.out.println("RedheadDuck!");
    }
}
  • RedheadDuck 클래스는 Duck 클래스를 상속.
  • 상속자에서 날기 동작과 울음소리를 정의.
    • 날기 동작과 울음소리는 인터페이스로 정의되어 있는 것에 클래스로 할당.
  • display()는 Duck 클래스에서 추상메소드로 정의하였기 때문에 클래스에서 정의.
💡 특정 구현에 맞춰서 프로그래밍하면 안됨.
하지만 new FlyWithWings() 처럼, 특정 객체에 구현되어 있는 구상 인스턴스를 만들었음. ⇒ 잘못되었지만 1장이라 이처럼 표현

동적으로 행동 지정하기

  • Duck 클래스에 Setter method 정의(setQuackBehavior(), setFlyBehavior())

두 클래스를 합치는 방법

💡 상속(as-is)보다는 구성(has-is)을 활용
  • A에는 B가 있다. ⇒ Duck 클래스에 FlyBehavior와 QuackBehavior가 있어서 Duck 클래스에 나는 행동과 꽥꽥거리는 행동을 위임. ⇒ 구성

전략패턴


알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해주는 패턴

클라이언트로부터 알고리즘을 분리해서 독립적으로 분리.

요약 :

변화가 생길 기능(행동:behavior)을 예상하여 이를 캡슐화(및 추상화) 한다.

캡슐화 후 각 기능별로 구현해두고(ex: fly 중 noWay, WithWing, Rocket 등)

각 객체별로 사용할 기능을 (쉽게) 교환 받는다.

각 기능들이 추상화되어 있어 재사용성이 좋고, 수정이 용이하다.

 

기억 추적 :

서브클래스는 단순히 super 클래스를 상속받는식의 구현이 아닌,

interface, abstract 등을 이용하여 기능의 동작만 받음.

각 기능은 인터페이스를 구현하여 미리 정의해두고 모델은 이를 조합하여 기능을 만들어가는 방식.

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Command Pattern  (2) 2024.06.03
[Design Pattern] Singleton Pattern  (0) 2024.05.31
[Design Pattern] Factory Pattern  (0) 2024.05.29
[Design Pattern] Decorator Pattern  (0) 2024.05.28
[Design Pattern] Observer Pattern  (0) 2024.05.27

+ Recent posts