策略模式概述
在策略模式中,我们可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法。在这里,每一个封装算法的类我们都可以称之为一种策略(Strategy),为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做规则的定义,而每种算法则对应于一个具体策略类。
策略模式主要目的是将算法的定义与使用分开,也就是将算法的行为和环境分开,将算法的定义放在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合“依赖倒转原则”。在出现新的算法时,只需要增加一个新的实现了抽象策略类的具体策略类即可
策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。策略模式是一种对象行为型模式,其基本的UML图如下:
在策略模式结构图中包含如下几个角色:
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
场景模拟
现在我们模拟公交车售票系统,公交车针对不同的乘客群体会给与不同的优惠措施;公交车基本价格为2元,学生群体给与8折优惠,普通乘客不打折,60岁的老年人给与免费措施。售票员根据不同的群体销售不同的车票,不同的人给与不同的优惠措施,就是采用了不同的策略。
我们将实际情况抽象三个类,一个时售票员类CConductor,一个票类CTicket,以及打折类CDiscount,整个系统的UML设计图如下:
这里的CTicket类充当环境类,CDiscount类是抽象策略类,CTicket要使用CDiscoun类中的接口,GetRate()函数就是抽象算法接口,执行不同的优惠策略有不同的优惠措施,下面给出具体的代码。
具体代码
#include "stdafx.h"
using namespace std;
//抽象策略类
class CDiscount
{
public:
CDiscount(){};
virtual ~CDiscount(){
int i = 0;
};
virtual float GetRate()const = 0; //策略算法接口
};
//普通票
class CCommonDiscount:public CDiscount
{
public:
virtual float GetRate() const
{
cout << "普通票不打折..." << endl;
return static_cast<float>(1.0);
}
};
//学生票
class CStudentDiscount:public CDiscount
{
public:
virtual float GetRate() const
{
cout << "学生票打8折..." << endl;
return static_cast<float>(0.8);
}
};
//老年票
class CElderDiscount:public CDiscount
{
public:
virtual float GetRate() const
{
cout << "老年票免费..." << endl;
return static_cast<float>(0.0);
}
};
//票类
class CTicket
{
public:
CTicket(float fPrice, CDiscount* pDiscount)
{
m_fOriginalPrice = fPrice;
m_pDiscount = pDiscount;
}
~CTicket()
{
SAFE_DELETE_PTR(m_pDiscount);
}
//对外接口,包装了不同策略
float GetPrice() const
{
float fActualPrice = 0.0;
if (m_pDiscount != NULL)
{
//策略执行
fActualPrice = m_fOriginalPrice * (m_pDiscount->GetRate());
}
return fActualPrice;
}
private:
CDiscount* m_pDiscount;//根据不同情况实施不同策略
float m_fOriginalPrice;
};
//售票员
class CConductor
{
public:
//依赖将类对象作为参数
void SaleTicket(const CTicket& ticket, int num)
{
cout << "total price:" <<ticket.GetPrice() * num << endl;
}
};
测试代码:
//售票员
CConductor conductor;
//创建票
CTicket CommonTicket(2,new CCommonDiscount);
conductor.SaleTicket(CommonTicket, 1);
//暴露 CCommonDiscount类,代改进
运行结果:
为了简化客户端测试代码,需要将折扣类隐藏,通过枚举变量来表示创建不同的折扣类;因此需要在CTicket类中引入CDiscountFactory简单工厂类,同时需要修改CTicket类的构造函数,代码如下:
//折扣类简单工厂
class CDiscountFactory
{
public:
static CDiscount* CreateDiscount(ENUM_TICKET_TYPE type)
{
CDiscount* pDiscount = NULL;
switch (type)
{
case EN_TICKET_COMMON:
pDiscount = new CCommonDiscount;
break;
case EN_TICKET_STUDENT:
pDiscount = new CStudentDiscount;
break;
case EN_TICKET_ELDER:
pDiscount = new CElderDiscount;
default :
break;
}
return pDiscount;
}
};
class CTicket
{
public:
CTicket(float fPrice, ENUM_TICKET_TYPE type)
{
m_fOriginalPrice = fPrice;
m_pDiscount = CDiscountFactory::CreateDiscount(type);
}
}
引入简单工厂模式后,客户端测试代码更加简洁,减少了具体折扣类的选择负担:
//售票员
CConductor conductor;
//创建票
CTicket CommonTicket(2,EN_TICKET_COMMON);
conductor.SaleTicket(CommonTicket, 1);
CTicket StudentTicket(2,EN_TICKET_ELDER);
conductor.SaleTicket(StudentTicket, 1);
CTicket ElerTicket(2,EN_TICKET_STUDENT);
conductor.SaleTicket(ElerTicket, 1);
策略模式总结
适用场景:
在以下情况下可以考虑使用策略模式:
(1) 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据“里氏代换原则”和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。
(2) 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
(3) 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。
模式缺点
(1) 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
(2) 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。