반응형
전략 패턴 이해를 위한 간단한 문제 제안
- 전략패턴을 이해하기 위해 간단한 문제를 먼저 제시해보도록 하겟습니다.
- “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 |