(헤드 퍼스트 디자인 패턴) 1. 전략 패턴

전략 패턴

  1. 오리 시뮬레이션 게임에서 다음 클래스 설계 요청
    • 이 게임에서 오리는 꽥꽥거리며 헤엄칠 수 있습니다.

오리 수업

public abstract class Duck {

    protected void quack(){
        System.out.println("꽥꽥");
    }

    protected void swim() {
        System.out.println("수영 할 수 있습니다.");
    }
    abstract void display();
}
  • 추상 클래스로 Duck 슈퍼클래스 만들기

MallardDuck.class

public class MallardDuck extends Duck {
    @Override
    public void display() {
        quack();
        swim();
        System.out.println("MallardDuck 입니다.");
    }
}

RedheadDuck.클래스

public class RedheadDuck extends Duck {
    @Override
    public void display() {
        quack();
        swim();
        System.out.println("레드 해드 덕입니다.");
    }
}
  • 수퍼클래스에서 상속되는 MallardDuck 및 RedheadDuck 생성

  1. 보드팀 기획의 변화 오리가 날 수 있도록 바꿔주세요!
public abstract class Duck {

    protected void quack(){
        System.out.println("꽥꽥");
    }

    protected void swim() {
        System.out.println("수영 할 수 있습니다.");
    }

		protected void fly() {
				System.out.println("날 수 있습니다.");
		}		

    abstract void display();
}
  • 모든 오리가 날 수 있도록 슈퍼 클래스에 fly() 메서드를 추가했습니다.
  • 하지만 여기는 문제는 오리가 날지 말아야 할 시간에 날고 있다는 것입니다.~였다
  • 또한, 목제인조오리는 소리를 내지 않아야 하는데 소리를 내는데 문제가 있다.

  1. 인터페이스 분리 원칙에 따라 감항 및 돌팔이 가능 인터페이스 분리
public interface Flyable {
	public void fly();
}
public interface Quackable {
	public void quack();
}
  • 하지만 이렇게 하면 재사용하지 않고 모든 코드를 덮어쓰는 문제로 기존 코드의 영향이 크다.원인이됩니다
  • 인터페이스 분리 원리 때문에 해야 한다고 생각했는데 좋은 방법은 아니었습니다.

디자인 원칙 1 애플리케이션에서 달라지는 부분을 찾아 그렇지 않은 부분과 분리합니다.하다.

  • 변경되는 부분을 따로 추출하여 캡슐화하고 변경되지 않는 부분에 영향을 주지 않고 해당 부분만 확장합니다. 가능한

디자인 원칙 2 구현 대신 인터페이스 프로그래밍 하다.

위와 같이 인터페이스에 너무 의존하는 클래스를 만드는 대신 인터페이스에 특정 동작을 정의하여 실제 동작 구현즉, 위의 형식에 따라 프로그래밍 당신은 할 수 있어야

public interface FlyBehavior {
	void fly();
}
public class FlyWithWings implements FlyBehavior {
	@Override
	void fly() {
		System.out.println("날 수 있어요!");
	}
}
public class FlyNoWay implements FlyBehavior {
	@Override
	void fly() {
		System.out.println("날 수 없어요!");
	}
}
  • 아래와 같이 액션으로 나뉩니다.
public abstract class Duck {
	FlyBehavior flyBehavior;

	public Duck() {}

	public void performFly() {
		flyBehavior.fly();
	}
}
  • 인터페이스에 추상 클래스 주입
public class MallardDuck extends Duck {
	public MallardDuck() {
		flyBehavior = new FlyWithWings();
	}

	public void display() {
		System.out.println("저는 물오리 입니다.");
	}
}
  • 이제 클래스별로 인터페이스에 맞는 구현이 구현되어 별도의 추가 없이 구현할 수 있습니다.
  • 지금까지의 과정에서 Duck 클래스에 정의된 메소드를 구현하지 않고, 다른 클래스에 위임하여 구현 가능나는했다.

  1. 오리 동작을 포함하는 코드

오리 수업

public abstract class Duck {
    QuackBehavior quackBehavior;
    FlyBehavior flyBehavior;

    public Duck(){}

		public abstract void display();

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

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

오리의 비행

public interface FlyBehavior {
    void fly();
}
---
public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("날 수 없습니다.");
    }
}
---
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("날 수 있습니다.");
    }
}
---

오리 울음

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("뀍 소리를 냅니다.");
    }
}
---
public class MuteQuack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("소리 낼 수 없습니다.");
    }
}

테스트 코드 및 결과

@Test
void performDuck(){
    Duck duck = new MallardDuck();
    duck.display();
    duck.performFly();
    duck.performQuack();
}

나는 물 오리입니다. 날 수 있다 삐걱거리는 소리를 낸다.

Duck 인터페이스는 performFly() 및 performQuack() 메서드에 quackBehavior 및 flyBehavior를 포함합니다. 이러한 캡슐화를 사용하여 MallardDuck은 깨끗한 객체 지향 코드 및 구현이지만 Duck 인터페이스를 사용하면 Liscop 대체 원칙을 유지할 수 있는 객체 지향 코드를 생성할 수 있습니다.


  1. 동작을 동적으로 지정

동적으로 수정할 수 있도록 기존 Duck.class에 setter를 추가합니다.

public abstract class Duck {
    QuackBehavior quackBehavior;
    FlyBehavior flyBehavior;

    public Duck(){}

		public abstract void display();

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

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

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

		public void setflyBehavior(FlyBehavior fb){
			flyBehavior = fb;
		}
}

테스트 코드 및 결과

@Test
void dynamicDuck(){
    Duck duck = new MockDuck();
    duck.display();
    duck.performFly();
    duck.performQuack();
    // 여기서 날수있도록 동적으로 변경
    duck.setFlyBehavior(new FlyRocketPowered());
    duck.performFly();
}

오리모형입니다. 날 수 없고 소리를 낼 수 없습니다. 로켓으로 날 수 있습니다.

원래는 모델의 오리라서 날 수 없었지만, 중간에 FlyBehavior에 동적으로 주입된 구현 객체를 변경하여 변경할 수 있었습니다.

디자인 원칙 3 상속 대신 구성 사용하다.

위의 내용은 이해가 되지 않을 수 있지만 여기의 구성은 다음과 같이 허용되어야 합니다. A는 B를 가지고 있다 위의 예를 보고 설명하면 오리는 비행 행동과 꽥꽥 행동이 있습니다. 즉, Duck 클래스에는 FlyBehavior 클래스와 QuackBehavior 클래스가 있으며, 이들 인터페이스를 위임하여 각각의 액션을 수행하는 것을 알 수 있다. 이렇게 두 클래스를 결합하십시오. 구성그것은 말한다.

그러나 중간에 구현을 변경하도록 허용하는 것은 안티 패턴입니다. 구현 개체가 초기 생성 시에만 설정되고 그 사이에 변경되지 않도록 하는 것이 중요합니다.


이를 통해 전략 패턴을 학습하게 되었으며, 전략 패턴의 정의는 다음과 같다.

전략 패턴동작에 따라 일련의 알고리즘을 정의하고 캡슐화하므로 모든 알고리즘 집합을 수정하고 사용할 수 있습니다. 전략 패턴을 사용하여 클라이언트에서 알고리즘을 분리하고 독립적으로 변경할 수 있습니다.
참조) 알고리즘 계열 : 각 인터페이스에 구현된 동작 집합입니다.


지금까지의 과정을 간단히 요약해 보자.

  1. 이전에는 오리가 헤엄치고 꽥꽥거리는 오리 시뮬레이션 게임이 있었습니다.
  2. 기획자들은 다른 회사들과 차별화하기 위해 오리가 날아가는 것까지 요구했다.
  3. → 이 과정에서 슈퍼클래스에 fly()라는 메소드를 추가하여 Duck을 물려받은 오리들이 모두 날아가는 상황이 발생하였다.
  4. 이러한 상황을 방지하기 위해 개발자는 인터페이스 분리의 원리이를 기반으로 Flyable이라는 인터페이스를 만들었고 날 수 있는 오리만이 Flyable 인터페이스를 구현했습니다.
  5. 하지만 이 경우 재사용할 수 없습니다. Flyable Duck 구현마다 Flyable을 구현해야 하는 상황이 발생했습니다. 당연히 Java의 기본 예약어를 사용하여 이제 인터페이스도 구현할 수 있습니다.
  6. 하지만 더 나은 방법을 찾기로 했습니다. 오리가 날아 울다 인터페이스에 동작 위임이렇게 하면 인터페이스를 구현하고 비행 및 비명 동작을 인터페이스에 추가하는 구현이 생성됩니다.
  7. FlyBehavior 및 QuackBehavior라는 오리의 동작과 Duck 추상 클래스에서 이러한 인터페이스에 위임된 동작을 위임하는 인터페이스를 만듭니다. 구현에서 주입을 가져오고 다형성을 만족시키기 위해 동작 자체를 캡슐화하는 추상 클래스를 만들었습니다.

이와 같이 동작을 클래스에 위임하는 클래스를 결합하는 방법인 컴포지션을 사용하여 유연성을 높입니다..