设计模式:策略模式(Strategy)

设计模式:策略模式(Strategy)

策略模式(Strategy)属于行为型模式(Behavioral Pattern)的一种。

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。

行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

行为型模式分为类行为型模式和对象行为型模式两种:

  1. 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
  2. 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。

模式动机

完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。在软件开发中也常常遇到类似的情况,实现某一个功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。

在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。

除了提供专门的查找算法类之外,还可以在客户端程序中直接包含算法代码,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。

模式定义

策略模式又称为政策模式(Policy),属于行为型模式。

策略模式定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。

模式结构

策略模式(Strategy)包含如下角色:

  • 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
  • 抽象策略(Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
  • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。

在这里插入图片描述

时序图

在这里插入图片描述

模式实现

环境类 Context.h:

#ifndef _CONTEXT_H_
#define _CONTEXT_H_

#include "Strategy.h"

class Context
{
private:
	Strategy* m_pStrategy;

public:
	void algorithm()
	{
		m_pStrategy->algorithm();
	}
	void setStrategy(Strategy* strategy)
	{
		m_pStrategy = strategy;
	}
	Strategy* getStrategy()
	{
		return m_pStrategy;
	}
};

#endif // !_CONTEXT_H_

抽象策略类 Strategy.h:

#ifndef _STRATEGY_H_
#define _STRATEGY_H_

class Strategy
{
public:
	virtual void algorithm() = 0;
};

#endif // !_STRATEGY_H_

具体策略A类 ConcreteStrategyA.h:

#ifndef _CONCRETE_STRATEGY_A_H_
#define _CONCRETE_STRATEGY_A_H_

#include <iostream>

#include "Strategy.h"

class ConcreteStrategyA : public Strategy
{

public:
	virtual void algorithm()
	{
		std::cout << "use algorithm A" << std::endl;
	}
};

#endif // !_CONCRETE_STRATEGY_A_H_

具体策略B类 ConcreteStrategyB.h:

#ifndef _CONCRETE_STRATEGY_B_H_
#define _CONCRETE_STRATEGY_B_H_

#include <iostream>

#include "Strategy.h"

class ConcreteStrategyB : public Strategy
{

public:
	virtual void algorithm()
	{
		std::cout << "use algorithm B" << std::endl;
	}
};

#endif // !_CONCRETE_STRATEGY_B_H_

在单线程环境下的测试

测试代码:

#include <iostream>
#include <stdlib.h>

#include "Context.h"
#include "ConcreteStrategyA.h"
#include "ConcreteStrategyB.h"
#include "Strategy.h"

using namespace std;

int main(int argc, char* argv[])
{
	Context* cxt = new Context();

	Strategy* s1 = new ConcreteStrategyA();
	cxt->setStrategy(s1);
	cxt->algorithm();

	Strategy* s2 = new ConcreteStrategyB();
	cxt->setStrategy(s2);
	cxt->algorithm();

	delete s1;
	delete s2;
	delete cxt;

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

在多线程环境下的测试

略。

模式分析

  • 策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
  • 在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
  • 策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

优缺点

优点:

  1. 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  2. 策略模式提供了管理相关的算法族的办法。
  3. 策略模式提供了可以替换继承关系的办法。
  4. 使用策略模式可以避免使用多重条件转移语句。

缺点:

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  2. 策略模式将造成产生很多策略类,所有策略类都需要对外暴露。

适用场景

在以下情况下可以使用策略模式:

  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

应用场景

  1. Java SE 中的每个容器都存在多种布局供用户选择。
  2. 出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。
  3. 超市促销可以釆用打折、送商品、送积分等方法。
  4. 当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

状态模式和策略模式的异同

在这里插入图片描述

在这里插入图片描述

相同点:

  1. 类图、类的结构一样。
    状态模式:Context环境类、State类(抽象状态类、具体状态类)
    策略模式:Context环境类、Strategy类(抽象策略类、具体策略类)
  2. 两种模式都是行为型模式,UML图相同,都是将核心类(State类或Strategy类)注入到Context类中,在客户端通过操作Context环境类间接操作核心类(State类或Strategy类),巧妙的在客户端屏蔽核心类。

不同点:

  1. 状态可以看作是Context类的一个内在属性,是必不可少的,新建Context类对象时,Context类的初始化函数中就要初始化状态属性;策略不是Context的属性,是Context类调用的一个外界的东西。
  2. 状态模式中一定含有“状态切换”逻辑,不管是在ConcreteState类中还是在Context类中,状态类中一定含有“状态切换”代码;策略模式只是简单的在客户端切换策略,核心类(Strategy类)中没有“切换”逻辑。

核心类(State类或Strategy类)是内在属性的时候是状态模式,是外界东西的时候是策略模式。
存在核心类(State类或Strategy类)切换逻辑的是状态模式,不存在的是策略模式。

模板方法模式和策略模式的区别

模板方法模式的主要思想:定义一个算法流程,将一些特定步骤的具体实现、延迟到子类。使得可以在不改变算法流程的情况下,通过不同的子类、来实现“定制”流程中的特定的步骤。

策略模式的主要思想:使不同的算法可以被相互替换,而不影响客户端的使用。

在思想和意图上看,模板方法更加强调:

  1. 定义一条线(算法流程),线上的多个点是可以变化的(具体实现在子类中完成),线上的多个点一定是会被执行的,并且一定是按照特定流程被执行的。
  2. 算法流程只有唯一的入口,对于点的访问是受限的。

策略模式更注重于: 一个“策略”是一个整体的(完整的)算法,算法是可以被整体替换的。而模板方法只能被替换其中的特定点,算法流程是固定不可变的。

模式优点缺点
策略模式横向扩展性好,灵活性高客户端需要知道全部策略,若策略过多会导致复杂度升高
模板方法模式可维护性好,纵向扩展性好耦合性较高,子类无法影响父类公用模块代码

模式扩展

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度:

在这里插入图片描述

参考

  1. https://design-patterns.readthedocs.io/zh-cn/latest/behavioral_patterns/behavioral.html
  2. https://www.runoob.com/design-pattern/strategy-pattern.html
  3. https://blog.csdn.net/weixin_45433817/article/details/131037102
  4. https://blog.csdn.net/zhaominyong/article/details/106241643
  • 17
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值