mingg IT

[디자인 패턴] 헤드퍼스트 디자인 패턴 1장 리뷰 본문

BackEnd

[디자인 패턴] 헤드퍼스트 디자인 패턴 1장 리뷰

mingg123 2023. 3. 6. 20:42

1장 전략 패턴

디자인 원칙

달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 '캡슐화' 한다

그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다

 

구현보다는 인터페이스에 맞춰서 프로그래밍 한다.

 

상속보다는 구성을 활용한다.

 

 

전략패턴

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘을 수정해서 쓸 수 있게 해준다.

전략패턴을 사용하면 클라이언트로 부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.

 

쓰면서 제대로 공부하기

애플리케이션을 만드는 과정에서 코드를 바꿔야 했던 이유

1. 고객이나 사용자가 다른 것을 요구하거나 새로운 기능을 원할 때

2. 회사에서 데이터베이스 종류를 바꾸고 데이터도 전과 다른 데서 구입하기로 했는데, 그게 지금 사용하는 데이터 포맷과 완전히 다른 경우

3. API 응답이 바뀔 경우

4. 새로운 라이브러리를 적용하게 될 경우 

5. 애초에 설계가 잘못 된 경우

 

 

오리 시뮬레이션 게임

기존 설계는 오리가 quack (꽥꽥 소리 지름), swim(수영) 을 하기때문에 슈퍼클래스로 작성하고,

오리의 모양은 다르기 때문에 display는 오버라이드 한다. 

 

추가 요구사항으로인해 fly() 메서드를 추가함.

Duck클래스에 fly() 메서드를 추가했더니, 고무 오리들도 날아다니기 시작함. 

Duck의 몇몇 서브클래스들만 날아야 하는데 날지 않아야 하는 오리들도 날기 시작함.

코드의 일부만 고쳤는데 프로그렘 전체에 고무오리가 날아다니는 문제가 발생함.

 

첫 번째 해결법 (상속)

fly 함수를 override 해서 날지 않도록 수정함. 

모든 서브클래스의 fly 함수를 override해줘야 하는 문제 발생. 

 

Duck행동을 상속할 때 단점이 될 수 있는 요소

A. 서브 클래스에서 코드가 중복된다.

B. 실행 시에 특징을 바꾸기 힘들다.

D. 모든 오리의 행동을 알기 힘들다.

F. 코드를 변경했을 때 다른 오리들에게 원치 않는 영향을 끼칠 수 있다. 

 

상속은 올바른 해결책이 아님

 

두 번째 해결법(인터페이스 설계하기)

fly()를 Duck 슈퍼클래스에서 제거하고, Flyable 인터페이스에 fly() 메소드를 선언 

날 수 있는 오리에게만 이 인터페이스를 구현해서 fly() 메소드를 넣어줌

 

처음 방법에 비해 override하지 않아도 되지만,

날아가는 동작이 바뀔 경우 Duck의 서브클래스를 모두 수정해줘야함.

코드 중복이 일어남.

 

 

세 번째 해결 방법

Duck 클래스에서 fly(), quack()을 제외하면 잘 동작했었기 때문에 바뀌는 부분인 fly, quack를 클래스 집합으로 분리한다.

 

구현보다는 인터페이스에 맞춰서 프로그래밍 한다.

 

fly()나, quack() 과 같은 오리의 행동은. 별도의 클래스 안에 들어가 있음.

Duck클래스에서는 오리의 행동을 구체적으로 구현할 필요가 없음.

 

왜 FlyBeBehavior를 만들때 추상 클래스를 사용하지 않고 인터페이스를 만드냐?

인터페이스에 맞춰서 프로그래밍한다는 것은 상위 형식에 맞춰서 프로그래밍 한다는 뜻임.

객체를 변수에 대입할 때 상위 형식을 구체적으로 구현한 형식이라면 어떤 객체든 넣을 수 있기 때문

 

이런식으로 디자인 하면 다른 객체에서도 나는 행동과, 꽥꽥 행동을 재사용할 수 있다. (Duck 클래스 안에 있지 않기 때문에)

 

쓰면서 제대로 공부하기

fly를 implement하는 로켓추진클래스에 만들고, fly() 함수에 로켓 추친으로 날아가는 기능 구현

오리 소리를 내는 함수를 사용하는 곳 

 

fly, quack 행동을 Duck 클래스에서 정의하지 않고 다른 클래스에 위임했음.

 

이제 Duck 클래스에서 FlyBehavior flyBehavior를 선언한 뒤

performQuack() 에게 꽥꽥거리는 행동을 위임한다. 

 

MallardDuck 인스턴스가 만들어질때, Duck으로부터 상속받은 flyBehavior 인스턴스 변수에 new FlyWithWing를 대입한다.

 

동적으로 행동 지정하기

MallardDuck이 날다가 날개를 다쳐서 날지 못하게 될 수도 있음.

 

Duck.java

package strategyPattern;

public abstract class Duck {

  public FlyBehavior flyBehavior;
  public QuackBehavior quackBehavior;

  public Duck() {}

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

  public void swim() {
    System.out.println("모든 오리는 수영한다.");
  }
  public abstract void display();

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

  // 동적으로 행동 지정하기
  public void setFlyBehavior(FlyBehavior fly) {
    flyBehavior = fly;
  }

  public void setQuackBehavior(QuackBehavior quack) {
    quackBehavior = quack;
  }
}

 

FlyBehavior.java

package strategyPattern;

public interface FlyBehavior {
  void fly();
}

 

FlyWithWings.java

package strategyPattern;
public class FlyWithWings implements FlyBehavior{

  @Override
  public void fly() {
    System.out.println("날고 있음");
  }

}

 

FlyNoWay.java

package strategyPattern;

public class FlyNoWay implements FlyBehavior{

  @Override
  public void fly() {
    System.out.println("못 난다");
  }
  
}

 

QuackBehavior.java

package strategyPattern;

public interface QuackBehavior {
  void quack();
}

 

Quack.java

package strategyPattern;

public class Quack implements QuackBehavior {

  @Override
  public void quack() {
   System.out.println("꽥꽥");
  }
  
}

 

MallardDuck.java

package strategyPattern;

public class MallardDuck  extends Duck{

  public MallardDuck() {
    flyBehavior = new FlyWithWings();
    quackBehavior = new Quack();
  }

  @Override
  public void display() {
  System.out.println("저는 물 오리 입니다.");
  }
  
}

 

main.java

package strategyPattern;

public class main {
  public static void main(String[] args) {
    Duck mallard = new MallardDuck();
    mallard.performFly();
    mallard.performQuack();

    mallard.setFlyBehavior(new FlyNoWay());
    mallard.performFly();
  } 
}

실행 결과

 

정리

오리가 나는 행동과, 꽥꽥거리는 행동을 캡슐화 하고 위임 받음. 

오리 클래스를 상속받는 대신, 구성되어 행동을 부여받음 

 

구성

두 클래스를 합치는 것

오리에는 FlyBehavior와 QuackBehavior가 있다.

 

 

전략 패턴 장점

공통 로직이 부모 클래스에 있지 않고, 행동 별로 클래스가 존재하기 때문에 구현체들간의 영향도가 줄어듬

인터페이스에 의존하고 있기 때문에 구현체를 갈아끼우기가 쉽다. 

 

전략패턴 단점

로직이 늘어날 때마다 작성해줘야 할 구현체 클래스가 늘어남. 

 

 

패턴과 전문용어 

서로 알고 있는 패턴으로 소통하면 패턴 이름, 패턴에 담긴 내용 등 함께 이야기할 수 있다.

다른 개발자간 정확하게 파악할 수 있다.

소프트웨어 시스템을 이야기할 때 패턴을 사용하면 객체와 클래스를 구현하는 것과 관련된 시간을 버릴 필요가 없어

디자인 수준에서 초점을 맞출 수 있다.

디자인 패턴 용어를 팀원이 잘 알고 있다면 오해의 소지가 줄어들어 작업을 빠르게 진행할 수 있다.

전문용어는 신입 개발자에게 훌룡한 자극제가 된다. 

 

 

 

Comments