《大话设计模式》C++实现之策略模式
写在前面
本篇博客是本人学习《大话设计模式》之后的一个总结分享。原书是用c#编写的伪代码,此处本人将使用C++语言来实现书中的各种设计模式。由于时间有限,此处仅贴出经过设计模式优化后的代码(也就是书中的最终版代码)。此处代码均为自己编写,个别借鉴了其他优秀大佬博主的代码逻辑,此代码仅供学习交流,不做其他商业用途。
前言
此段代码在实现时有两个值得注意的小细节:1、使用了#ifndef、#define、#endif。2、使用了nullptr。这两点是自己在日常写代码时发现并积累的两个知识点,在文章最后会做一个简单的小总结。好了,废话不多说,正式进入今天的策略模式!
策略模式
在我主观感受上,策略模式和简单工厂模式十分相像,实际客观来讲从代码结构上也是十分相似的。他们都采用了抽象出一个基类,然后不同子类继承该基类分别实现各自的算法,再通过一个context(上下文)类来整合所有的方法(在简单工厂模式中是工厂(Factory)类)。但它与工厂类也有不同,context除了引入基类对象之外,也在类内定义了一个函数用来调用基类的函数,这样做的目的是当我们在客户端(主函数)中进行相关操作时,我们只需实例化context类的对象,也就是说我们的主函数只需要认识context类就可。因此在策略模式下的代码实现的进一步解耦合,解耦的目的就是使各部分之间的依赖降低,因此当其中某部分代码发生变动时,其他的代码无需变动或者无需大的变动,让代码维护和扩展更加便利。
策略模式与简单工厂模式还有一个不同之处,来自于策略模式比较权威的定义
意思是策略模式就是用来封装“算法”的,这个算法我们更加广义来讲可以理解为各种类型的规则。只要在分析时听到需要在不同时间应用不同的业务规则,就可以考虑用策略模式来处理这种变化的可能性。以下为完整代码:
#ifndef STRATEGY_H
#define STRATEGY_H
#include <string>
#include <math.h>
#include <iostream>
#include <stdlib.h>
//基类抽象类
class CashSuper
{
public:
virtual double acceptCash(double money) = 0;
};
//子类:正常付费类型
class CashNormal :public CashSuper
{
public:
double acceptCash(double money)
{
return money;
}
};
//子类:返现类型
class CashReturn :public CashSuper
{
private:
double moneyCondition;
double moneyReturn;
public:
CashReturn(double moneyCondition, double moneyReturn)
{
this->moneyCondition = moneyCondition;
this->moneyReturn = moneyReturn;
}
double acceptCash(double money)
{
double result = money;
if (money > moneyCondition)
result = money - floor(money / moneyCondition) * moneyReturn;
return result;
}
};
//子类:打折扣类型
class CashRebate :public CashSuper
{
private:
double moneyRebate;
public:
CashRebate(double moneyRebate)
{
this->moneyRebate = moneyRebate;
}
double acceptCash(double money)
{
return money * moneyRebate;
}
};
#endif STRATEGY_H
#ifndef CASHCONTEXT_H
#define CASHCONTEXT_H
class CashContext //context上下文类
{
private:
CashSuper* cs; //引入基类
public:
CashContext(int type) :cs(NULL)
{
switch (type)
{
case 1:
{
cs = new CashNormal();
break;
}
case 2:
{
cs = new CashReturn(300, 100);
break;
}
case 3:
{
cs = new CashRebate(0.8);
break;
}
default:;
}
}
~CashContext()
{
if (cs != NULL)
{
delete cs;
cs = nullptr;
}
}
double GetResult(double money)
{
return cs->acceptCash(money); //调用基类函数
}
};
#endif
void main()
{
//double total = 0;
double totalPrices = 0;
//正常收费
CashContext* cc1 = nullptr; //只需实例化context类
cc1 = new CashContext(1);
totalPrices = cc1->GetResult(800);
//total += totalPrices;
std::cout << "Type:正常收费 totalPrices:" << totalPrices << std::endl;
totalPrices = 0;
//返现类型
CashContext* cc2 = nullptr; //只需实例化context类
cc2 = new CashContext(2);
totalPrices = cc2->GetResult(800);
//total += totalPrices;
std::cout << "Type:满300返100 totalPrices:" << totalPrices << std::endl;
totalPrices = 0;
//打折类型
CashContext* cc3 = nullptr; //只需实例化context类
cc3 = new CashContext(3);
totalPrices = cc3->GetResult(800);
//total += totalPrices;
std::cout << "Type:打8折 totalPrices:" << totalPrices <<std::endl;
totalPrices = 0;
if (cc1 != NULL)
{
delete cc1;
cc1 = nullptr;
}
if (cc2 != NULL)
{
delete cc2;
cc2 = nullptr;
}
if (cc3 != NULL)
{
delete cc3;
cc3 = nullptr;
}
system("pause");
}
同样的,此处代码中的各个类都应当分文件编写然后包含对应的文件名,此处为了便利就都贴在一起。
谈谈前言的两个问题
1、关于#ifndef、#define、#endif的使用
这三个宏的作用是为了避免同一个文件被include多次。观察上面的代码我们可以发现,很多子类和其他类中声明了一个基类的对象,也就意味着,会有很多文件需要#include基类才可让本类识别出基类。但当进行编译时由于基类被重复编译导致里面的定义被重复编译从而产生大量的声明冲突,于是编译器报错。使用#ifndef、#define、#endif就是保证该类只被编译一次。大概的结构如下:
#ifndef <标识>
#define <标识>
…
#endif
标识的命名规则一般是头文件名全大写,并把文件名中的“.”也变成下划线,如: stdio.h写成STDIO_H
因为我自己是使用的vs2019,在新建头文件(.h文件)时会自动生成一行#pragma once,这行代码的作用与以上三个宏一样,他们俩的区别目前不详,但我在使用的过程中的确出现过用#pragma once不起作用,用三个宏才解决问题的情况。但是不管怎么说,当一个文件需被多次#include时,编译报错有声明冲突、重复定义时应当考虑是否加上三个宏或者#pragma once,两个可以都试一下。
2、关于nullptr的问题
大家可以看到,我在释放指针内存时多次使用了nullptr,以往我们都是将NULL赋值给指针,但在此处我建议大家,只要自己的编译器支持C++11特性的都应该首先使用nullptr来替代NULL的宏定义,正常使用过程中他们是完全等价的。目前市面上最新版的各种编译器都应该是支持了C++11。
原因:在c++环境下,NULL被直接定义为一个整型 0。 在大多数情况下这并不会产生什么问题,但是万一有重载或者模板推导的时候,编译器就无法给出正确结果了。
void call_back_process(CCObject* target, void* data);
bind(call_back_process, target, NULL);
// error 报错:函数类型是void* ,但是我们绑定的是一个整型 0