CHAPTER 01.디자인 패턴 소개와 전략 패턴
디자인 패턴

CHAPTER 01.디자인 패턴 소개와 전략 패턴

반응형

전략 패턴 이해를 위한 간단한 문제 제안

  • 전략패턴을 이해하기 위해 간단한 문제를 먼저 제시해보도록 하겟습니다.
  • “SIMUDuck”이라는 오리 게임을 만들기 위해 오리라는 객체를 정의하고 오리를 상속받아 도메인에 맞는 오리를 생성하는 설계 구조입니다.

  • 이런 설계 상황에서 기획자의 요구에 의해 Duck이라는 객체에 Fly()라는 메소드를 추가 받아야 하는 요구사항이 들어온것입니다.
  • 개발자는 상속을 사용하여 구현한 상태에서 모든 오리는 날수 있다고 생각하여 Fly()라는 메소드를 추가하게 됩니다.
  • 근데 이런 상황에서 만약 모형 오리라는 클래스를 추가하게 되면 날지 못하는 오리들도 날게 되는 기이한? 오류가 발생하게 됩니다.
  • 이처럼 전략패턴은 상속에 대한 부정적인 관점으로 인터페이스 기반 설계와 구현을 지향합니다.

전략 패턴

전략 패턴 이란?

  • 전략 패턴은 알고리즘군을 정의하고 캡슐화하여 각각의 알로리즘군(변화하는 영역)을 수정해서 쓸 수 있는것을 의미합니다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘군을 분리해서 독립적으로 변경할 수 있습니다.
  • 지금 부터는 위의 문제되는 상황을 전략 패턴을 사용하여 변경하는 영역을 분리한 뒤 아래와 같은 설계로 변경하도록 해보겠습니다.

  • 변하는 부분인 Fly와 Quack을 분리함으로써 Duck을 상속하던 서브클래스는 Fly와 Quack이 필요할때만 인터페이스를 구현하여 필요할 때만 사용할 수 있습니다.
  • 이는 디자인 첫 번째 원칙중 애플리케이션에서 달라지는 부분을 찾아낸뒤 달라지지 않는 부분과 분리하는 법칙을 지킨것입니다.
    • 위에 말을 바꾸면 바뀌는 부분은 따로 뽑아서 캡슐화하게 된다면 나중에 문제 상황처럼 변경 사항이 발생할 경우 바뀌지 않는 부분에는 영향을 주지않고 바뀌는 부분만 고치거나 확장할 수 있습니다.
    • 바뀌는 부분과 바뀌지 않는 부분을 바꾸는것은 모든 디자인 패턴을 이루는 기반 원칙이므로 매우 중요한 개념입니다.
  • 두 번째로 구현보다는 인터페이스에 맞춰서 프로그래밍을 해야합니다.
    • 이는 변하는 부분을 인터페이스로 정의하여 변경되는 부분을 정의하여 구현하는 방식을 의미합니다.
    • 반드시 인터페이스일 이유는 없습니다. 추상 클래스를 사용해도 괜찮습니다.
    • 핵심은 상위 형식에 맞춰서 프로그래밍 할 수 있는 방식을 사용하는것입니다.

구현

  • 위의 내용들을 직접 구현해보도록 하겠습니다.
package com.example.designpattern.strategy;

public class MallardDuck extends Duck {

   public MallardDuck() {
			//오리가 사용할 행동을 정의
      quackBehavior = new Quack();
      flyBehavior = new FlyWithWings();

   }

   public void display() {
      System.out.println("I'm a real Mallard duck");
   }
}
  • 먼저 자신이 생성할 오리의 객체를 정의합니다.
  • 이때 오리가 사용할 행동을 정의합니다.(new Quack(), new FlyWithWings()을 의미)
  • 만약 MallarDuck이 행동을 변경하고 싶다면 행동에 대한 객체만 변경해주면 되기 때문에 위의 코드는 유연한 상태의 코드입니다.
  • 또한 Duck의 추상 클래스를 정의하였기 때문에 객체간의 결합에도 유연한것을 볼 수 있습니다.
package com.example.designpattern.strategy;

public abstract class Duck {
	//행동을 담당하는 인터페이스 형식의 레퍼런스를 선언합니다.
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;

	public Duck() {
	}

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

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

	abstract void display();

	public void performFly() {
		//행동 클래스에 위임하여 행동을 수행합니다.
		flyBehavior.fly();
	}

	public void performQuack() {
		//행동 클래스에 위임하여 행동을 수행합니다.
		quackBehavior.quack();
	}

	public void swim() {
		System.out.println("All ducks float, even decoys!");
	}
}
  • 먼전 Duck 클래스입니다.
  • 위에서 설명한대로 인터페이스 기반의 설계를 진행했기 때문에 Duck 클래스는 행동 클래스에 영향을 받지 않은 상태로 수행됩니다.
package com.example.designpattern.strategy;

public interface FlyBehavior {
	public void fly();
}
package com.example.designpattern.strategy;

public class FlyWithWings implements FlyBehavior {
	public void fly() {
		System.out.println("I'm flying!!");
	}
}
package com.example.designpattern.strategy;

public class FlyNoWay implements FlyBehavior {
	public void fly() {
		System.out.println("I can't fly");
	}
}
  • 위 3개의 코듣들은 나는 동작에 대한 클래스와 서브클래스들입니다.
  • 나는 동작에 대한 인터페이스를 구현하여 상위 프로그래밍에 초점을 맞춘 상태로 구현한것을 볼 수 있습니다.
  • 이를 통해 오리 객체를 만들때 자신이 원하는 동작의 클래스를 결합하여 사용할 수 있게 구현되어있습니다.
package com.example.designpattern.strategy;

public interface QuackBehavior {
	public void quack();
}
package com.example.designpattern.strategy;

public class Quack implements QuackBehavior {
	public void quack() {
		System.out.println("Quack");
	}
}
package com.example.designpattern.strategy;

public class MuteQuack implements QuackBehavior {
	public void quack() {
		System.out.println("<< Silence >>");
	}
}
package com.example.designpattern.strategy;

public class Squeak implements QuackBehavior {
	public void quack() {
		System.out.println("Squeak");
	}
}
  • 행동을 나타내는 Fly() 설명과 동일합니다.
package com.example.designpattern.strategy;

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

	}
}
  • MallardDucky의 perfromQuack()메소드가 실행됨으로서 객체는 QuackBeHavior에 행동을 위임한다.
  • 이를 통해 Quack()의 quack() 메소드가 실행됩니다.
  • performFlay()도 위와 동일한 메커니즘으로 진행됩니다.
  • 현재 코드가 유연한 상태라는것은 실행중에 동작을 바꿀수 있다는것으로 증명이 가능합니다.
    • model.setFlyBeHaviory()를 통해 실행중 나는 동작의 변경이 가능합니다.
  • 현재 구현 방식을 보면 오리 클래스와 행동 클래스가 결합이 되어 수행되는 것을 확인할수 있는데 **이처럼 두 클래스가 합쳐져서 수행하는 것을 구성(Composition)**이라고 합니다.
  • 구성은 매우 중요하며 세 번째 디자인 원칙입니다.

정리

  • 디자인 패턴은 코드가 아닌 경험을 재사용하는것이라고 말합니다.
  • 또한, 디자인 패턴은 만드는것이 아니라 발견하는것이라고 말합니다.
  • 아래의 객체지향 원칙을 항상 고려하세요
    • 바뀌는 부분은 캡슐화한다.
    • 상속보다는 구성을 지향해라.
    • 구현보다는 인터페이스에 맞춰 프록그래밍해라.
  • 패턴의 훌륭한 객체지향 디장인 품질을 갖추고 있는 시스템을 만드는 방법을 제공합니다.
  • 패턴을 사용함으로써 다른 개발자와의 의사소통이 편리해집니다.
    • 위에서 구현한 전략패턴을 경험으로 개발자와의 대화에서 “나는 전략패턴을 사용했어”라는 대화가 있다면 상대방의 코드가 어떻게 구성되어있는지 짐작할 수 있습니다!
  •  
반응형

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

CHAPTER 05.싱글톤 패턴  (0) 2023.05.06
CHAPTER 04.팩토리 패턴  (0) 2023.05.06
CHAPTER 03.데코레이터 패턴  (0) 2023.01.22
CHAPTER 02.옵저버 패턴  (0) 2023.01.22
CHATER 00.이 책을 읽는 방법  (0) 2023.01.22