본문으로 건너뛰기

디자인 패턴: 스트래티지 패턴 (Strategy Pattern)

무료2015-03-06#Design_Pattern#策略模式#Strategy Pattern

디자인 패턴은 코드 구조 최적화 경험에서 추출된 이론적 지식이며, 성숙한 디자인 패턴을 적용하면 코드의 재사용성, 확장성, 유지보수성을 향상시킬 수 있습니다. 그 중 스트래티지 패턴은 가장 기본적인 디자인 패턴 중 하나로, 간단히 말하면 교체 가능한 알고리즘 단계를 알고리즘 패밀리로 캡슐화하여 런타임에 동적으로 선택할 수 있도록 하는 것입니다.

no_mkd

일.스트래티지 패턴 (Strategy Pattern) 이란?

글자 그대로 이해하면, 스트래티지 패턴은 어떤 '스트래티지'를 적용한 디자인 패턴이며, 이 '스트래티지'란:변하는 부분을 캡슐화하는 것입니다.

사실 이 이해에는 오류가 있으며, 예시에는 큰 잘못이 없지만 이 패턴에 대한 이해에 편차가 있었습니다. 수정 내용은本文末尾에 추가했습니다.여기를 클릭하여 이동>>

이.예를 들자면

지금 Dog 를 나타내는 클래스를 만들어야 한다고 가정해 봅시다. 먼저, 모든 Dog 에게는 외형 (예: Color) 이 있고, 행동 (예: Run) 이 있습니다. 그래서 우리는 자연스럽게 이러한 Dog 베이스 클래스를 정의합니다:

public abstract class Dog {
	public abstract void display();//显示 Dog 的外形
	public abstract void run();//定义 Dog 的 Run 行为
}

다른 Dog 구체 클래스는 모두 Dog 베이스 클래스를 상속하면 되지만, 곧 새로운 요구사항이 왔습니다: Red Dog 가 있습니다. 이것은 애완견으로, 천천히 달리고 울음소리도 작습니다. 그래서 RedDog 클래스를 정의했습니다:

public class RedDog {
	 @override
	public void display(){
		System.out.println("this is a red dog.");
	}
	 @override
	public void run(){
		System.out.println("it's running slowly.");
	}
}

이어서 대량의 요구사항이 왔습니다. Black Dog, Gray Dog, Pink Dog, BlackGrayDog, PinkGrayDog。。。총 100 마리의 Dog 로, Color 가 다를 뿐만 아니라 Run 속도도 다릅니다. 그래서 100 가지 유형의 다른 Dog 를 나타내기 위해 100 개의 클래스를 정의했고, 각 클래스에서 Run 메서드와 Display 메서드를 구현했습니다.

嗯~、클래스 그림을 봅시다. 맞습니다. 이 BigBang 이 우리의 클래스 그림입니다. 매우 합리적으로 보입니다. 100 종의 Dog 가 있으면 100 개의 클래스가 있는 것은 당연하고, 구체적인 Dog 클래스는 Dog 베이스 클래스를 상속해야 하지 않을까요?嗯、그런 것 같습니다. 그리고 이렇게 해도 나쁜 점은 없어 보입니다. 적어도 현재 시점에서는 어떤 부적절한 점도 보이지 않습니다。。。

또 새로운 요구사항이 왔습니다. Dog 는 Run 뿐만 아니라 Bark 도 할 수 있다는 것을 알게 되었습니다. 자연스럽게, Dog 베이스 클래스에 새로운 행동 Bark 를 정의하고 100 개의 구체 클래스에서 Bark 메서드를 하나씩 오버라이드했습니다。。。과정은 조금 번거로웠지만 문제를 해결했습니다. 이제 모든 Dog 가 Bark 할 수 있습니다.

어느 날, 새로운 Dog 가 왔습니다. ToyDog 라고 하며, 장난감 개로, Run 도 Bark 도 할 수 없고 그냥 장난감일 뿐입니다. 그래서 ToyDog 가 Dog 베이스 클래스를 상속하고 Run 메서드와 Bark 메서드를 오버라이드했습니다 (메서드 본문은 비어 있습니다. Toy 는 Run 도 Bark 도 할 수 없기 때문에). 문제도 완벽하게 해결된 것처럼 보였습니다. 적어도 현재 시점에서는 어떤 문제도 없는 것 같습니다.

며칠 후, ToyDog 공장이 기술 혁신을 하여 새 제품에 배터리가 장착되어 Bark 할 수 있게 되었습니다. 문제 없습니다. ToyDog 클래스를 수정하여 Bark 를 구현했습니다. ToyDog 를 초기화하면 신나게 BarkBarkBark 하고 울립니다. 그러나 잠시 후 배터리가 방전되어 ToyDog 는 Bark 할 수 없게 되었지만, 우리의 ToyDog 클래스는 아직 Bark 할 수 있다고 표시합니다。。。

이제 마침내 문제가 발생했습니다. Dog 문제에서 가장 처리하기 어려운 부분이 Run 과 Bark 이 두 가지 행동이라는 것을 알게 되었습니다. 변화가 발생하면 구체적인 Dog 클래스를 수정해야 하고, 심지어 Dog 베이스 클래스까지 수정해야 합니다. 많은 시간을 소비할 뿐만 아니라 모든 구체적인 Dog 클래스에서 Run 과 Bark 메서드를 구현하여 매우 비대해 보입니다. 또한 가장 중요한 문제는 Dog 의 행동을 동적으로 수정할 수 없다는 것입니다. 예를 들어 작은 Dog 는 천천히 달리고 울음소리가 작지만, 자라면 빠르게 달리고 울음소리가 커지는 것이나, 위에서 언급한 배터리 방전으로 인한 행동 변화 문제에도 대응할 수 없습니다。。。。이러한 일련의 문제는 우리에게 한 가지를 보여주려 합니다: 이것은 처음부터 잘못된 설계였다는 것입니다.

삼.스트래티지 패턴을 어떻게 적용하는가?

스트래티지 패턴은 변하는 부분을 캡슐화할 것을 요구합니다. 먼저, 코드 내에서 빈번하게 변하는 부분을 찾아야 합니다. 이전 예시에서 변하는 부분은 무엇일까요?

1.Run 행동; 2.Bark 행동; 3.기타 존재할 수 있는 행동

이제 이러한 행동을 캡슐화합니다 (Run 을 예로):

package StrategyPattern;

/**

  • @author ayqy
  • 定义 Run 接口

*/ public interface IRun { public void run();//定义 run 行为 }

package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunQuickly 行为
 *
 */
public class RunQuickly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running quicky.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunSlowly 行为
 *
 */
public class RunSlowly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running slowly.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现 RunNoWay 行为
 *
 */
public class RunNoWay implements IRun{

	 @Override
	public void run() {
		System.out.println("it isn't able to run.");
	}

}

Bark 행동의 캡슐화도 이와 유사합니다. '변화'를 캡슐화한 후, Dog 베이스 클래스도 그에 따라 변경해야 합니다:

package StrategyPattern;

/**

  • @author ayqy
  • 定义 Dog 基类

*/ public abstract class Dog { IBark bark; IRun run;

public abstract void display();//显示 Dog 的外形

public IBark getBark() {
	return bark;
}

public void setBark(IBark bark) {
	this.bark = bark;
}

public IRun getRun() {
	return run;
}

public void setRun(IRun run) {
	this.run = run;
}

}

주의하세요. 우리는 변하기 쉬운 부분 (Bark 와 Run) 만 캡슐화했고, Display 메서드는 캡슐화하지 않았습니다 (Dog 의 외형은 비교적 fixed 할 것입니다). '변하는 부분'이란 무엇일까요? 이는 신중하게 고려해야 하며, 구체적인 상황에 따라 다릅니다.

이제 새로운 구체적인 Dog 클래스를 봅시다.

package StrategyPattern;

/**

  • @author ayqy
  • 实现 RedDog

*/ public class RedDog extends Dog{

public RedDog()
{
	//红狗是宠物狗
	//跑得慢,叫声小
	super.setRun(new RunSlowly());
	super.setBark(new BarkQuietly());
}

 @Override
public void display() {
	System.out.println("this is a red dog.");
}

}

클래스가 분명하게 슬림해졌습니다. 게다가 이제 런타임에 Dog 의 행동을 동적으로 수정할 수도 있습니다. 스트래티지 패턴의 위력을 보셨나요.

사.효과 예시

테스트 클래스를 작성합니다:

package StrategyPattern;

/**

  • @author ayqy
  • 测试策略模式<br/>
  • 策略模式,简单的说就是要求把“变化”封装起来,以隔离“变化”对其它部分的影响

*/ public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
	System.out.println("------- 创造一只 Red Dog -------");
	Dog redDog = new RedDog();
	showDogInfo(redDog);
	
	System.out.println("\n------- 创造一只 Black Dog -------");
	Dog blackDog = new BlackDog();
	showDogInfo(blackDog);
	
	System.out.println("\n------- 创造一只 Toy Dog -------");
	Dog toyDog = new ToyDog();
	showDogInfo(toyDog);
	
	System.out.println("\n------- 技术革新,现在 Toy Dog 可以小声叫了 -------");
	toyDog.setBark(new BarkQuietly());
	showDogInfo(toyDog);
	
	//上面的代码并不是最优的,只是为了说明策略模式
	//一个很明显的问题是 Dog redDog = new RedDog();
	//我们在面向具体对象编码,而不是设计模式所提倡的面向接口编码
	//可以定义一个 Dog 工厂来生产 Dog 对象以求模块之间更松的耦合
}

/**
 * @param dog
 * 显示 Dog 相关信息
 */
private static void showDogInfo(Dog dog)
{
	dog.display();
	dog.run.run();
	dog.bark.bark();
}

}

실행 결과 예시:

오.요약

스트래티지 패턴의 핵심은 빈번하게 변하는 부분을 캡슐화하는 것이며, 그 역할은 변하는 부분의 영향을 격리하여 국부적인 변화가 다른 fixed 부분에 영향을 미치는 것을 방지하는 것입니다. 설계에는 더 많은 시간이 소요될 수 있지만, 유지보수, 재사용, 확장이 용이합니다. 이 예시에서 Run, Bark 행동은 새로운 클래스 (예: Pig) 에서도 재사용할 수 있습니다. 행동이 변하면 각 행동 인터페이스만 수정하면 되고, 최대해 봐야 Dog 베이스 클래스를 간단히 수정하면 변화에 대응할 수 있습니다.

육.수정

위의 내용에는 약간의 편차가 있지만, 어떻게든 볼 만한 수준입니다. 다만 너무 정확하지는 않습니다. 아래에 정확한 설명을 합니다:

스트래티지 패턴의 사상은 확실히 캡슐화이지만, 여기에서의 '스트래티지'는 '변화를 캡슐화하는 것'을 의미하지는 않습니다. 이는 원문을 여러 번 읽은 후 발견했습니다 (『Head First 디자인 패턴』1 장은 약간 이해하기 어렵고, 첫 번째 이해가 편향되었습니다).

여기서의'스트래티지'는 알고리즘에 근사합니다.

스트래티지 패턴은 이해하기 어렵습니다 (필자는 원문을 그대로 베끼는 것을 극력 피했지만, 만일 다시 이해가 편향되어 남을 오도할까 봐 두렵습니다..). 책의 정확한 정의는 다음과 같습니다:

스트래티지 패턴은 알고리즘 패밀리를 정의하고, 각각을 캡슐화하여 서로 교체 가능하게 합니다. 이 패턴은 알고리즘의 변화가 알고리즘을 사용하는 클라이언트로부터 독립적이게 합니다.

개인이 여러 번 읽은 후, 원문이 표현하고자 하는 것은 다음과 같다고 생각합니다:

  • 1.스트래티지 패턴의 핵심은 알고리즘의 캡슐화입니다 (다른 디자인 패턴으로 '템플릿 메서드 패턴'이 있으며, 이 또한 알고리즘의 캡슐화입니다. 차이와 연락은 안에서 소개합니다.여기를 클릭하여 이동>>)
  • 2.알고리즘 (스트래티지) 의 선택 구현에 주력하고, 런타임에 스트래티지를 동적으로 변경하는 것을 지원합니다
  • 3.구체적인 구현은 변하는 부분을 찾아 인터페이스로 정의하는 것입니다. 각 인터페이스는 한 세트의 알고리즘에 해당하며, 각각이 하나의 스트래티지입니다
  • 4.동일한 인터페이스 하의 알고리즘은 서로 교체 가능합니다
  • 5.알고리즘은 클라이언트 코드로부터 독립적이며, 이는 알고리즘 캡슐화의 구체적인 현시입니다

댓글

아직 댓글이 없습니다

댓글 작성