使用策略模式和工厂方法改进if-else的实现
假设我们要做一个外卖平台,有这样的需求:
1、外卖平台上的某家店铺为了促销,设置了多种会员优惠,其中包含超级会员折扣8折、普通会员折扣9折和普通用户没有折扣三种。
2、希望用户在付款的时候,根据用户的会员等级,就可以知道用户符合哪种折扣策略,进而进行打折,计算出应付金额。
3、随着业务发展,新的需求要求专属会员要在店铺下单金额大于30元的时候才可以享受优惠。
4、接着,又有一个变态的需求,如果用户的超级会员已经到期了,并且到期时间在一周内,那么就对用户的单笔订单按照超级会员进行折扣,并在收银台进行强提醒,引导用户再次开通会员,而且折扣只进行一次。
那么,我们可以看到以下伪代码:
double calPrice(double orderPrice, String buyerType) {
if (用户是专属会员) {
if (订单金额大于30元) {
returen 7折价格;
}
}
if (用户是超级会员) {
return 8折价格;
}
if (用户是普通会员) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
return 原价;
}
以上,就是对于这个需求的一段价格计算逻辑,使用伪代码都这么复杂,如果是真的写代码,那复杂度可想而知。
这样的代码中,有很多if-else,并且还有很多的if-else的嵌套,无论是可读性还是可维护性都非常低。
那么,如何改善呢?
首先定义一个借口:
class UserPayService
{
public:
virtual ~UserPayService() {}
virtual double quote(double orderPrice) = 0;
};
接着定义几个策略类:
class ParticularlyVipService : public UserPayService
{
public:
double quote(double orderPrice)override
{
if (orderPrice > 30)
{
return 0.7*orderPrice;
}
}
};
class SuperVipPayService : public UserPayService
{
public:
double quote(double orderPrice)override
{
return 0.8*orderPrice;
}
};
class VipPayService : public UserPayService
{
public:
double quote(double orderPrice)override
{
if (hasTempCostRight)
{
hasTempCostRight = false;
return 0.8*orderPrice;
}
return 0.9*orderPrice;
}
bool hasTempCostRight{ true };
};
在引入了策略之后,我们可以按照如下方式进行计算:
#include"Strategy_Factory.h"
#include<iostream>
int main()
{
UserPayService* ups = new ParticularlyVipService();
double quoteInst = ups->quote(300);
std::cout << "ParticularlyVipService quote = " << quoteInst << std::endl;
ups = new SuperVipPayService();
double quoteInst2 = ups->quote(300);
std::cout << "SuperVipPayService quote = " << quoteInst2 << std::endl;
ups = new VipPayService();
double quoteInst3 = ups->quote(300);
std::cout << "VipPayService quote = " << quoteInst3 << std::endl;
return 0;
}
由此我们就是使用策略模式根据new出的不同会员的策略类进行不同的计算价格方法。
但是在真实的项目中,我们需要先得到用户的会员等级,比如从数据库中查出会员的等级,然后根据等级获取不同的策略类执行计算价格方法。那么真真正的计算价格的方法伪代码应该是:
double calPrice(double orderPrice,User user) {
String vipType = user.getVipType();
if (vipType == 专属会员) {
//伪代码:从Spring中获取超级会员的策略对象
UserPayService strategy = new ParticularlyVipService();
return strategy.quote(orderPrice);
}
if (vipType == 超级会员) {
UserPayService strategy = new SuperVipPayService();
return strategy.quote(orderPrice);
}
if (vipType == 普通会员) {
UserPayService strategy = new VipPayService();
return strategy.quote(orderPrice);
}
return 原价;
}
我们知道策略模式的一个缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
也就是说,虽然在计算价格的时候没有if-else了,但是选择具体的策略的时候还是不可避免的还是要有一些if-else。
为了改进这一点,我们可以定义并暴露统一稳定的接口,将内部可能变化的实现隔离,面向接口而非实现,这样客户端不需对所有策略有过多的了解,只要提供接口的入参即可得到相应的策略。(客户端只要提供用户类型标识,即可得到相应的策略)
对于会员的策略对象我们可以使用工厂模式获取:
创建一个工厂类:
class UserPayServiceStrategyFactory
{
private:
static std::unordered_map<std::string, UserPayService*> services;
public:
static UserPayService* getByUserType(std::string userType)
{
return services[userType];
}
static void reg(std::string userType, UserPayService* userPayService)
{
= std::make_pair(userType, userPayService);
services.insert(newService);
}
};
std::unordered_map<std::string, UserPayService*> UserPayServiceStrategyFactory::services;
此时我们可以由会员类型直接由工厂得到会员对象(指针),并以此(策略方法)计算得到应付价格:
//注册对象指针表
UserPayServiceStrategyFactory userPayService;
UserPayService* superVipPay = new SuperVipPayService();
userPayService.reg("SuperVip", superVipPay);
UserPayService* vipPay = new VipPayService();
userPayService.reg("Vip", vipPay);
UserPayService* particularlyVipPay = new ParticularlyVipService();
userPayService.reg("ParticularlyVip", particularlyVipPay);
double quoteInst1 = userPayService.getByUserType("ParticularlyVip")->quote(300);
std::cout << "ParticularlyVipService quote = " << quoteInst1 << std::endl;
double quoteInst2 = userPayService.getByUserType("SuperVip")->quote(300);
std::cout << "SuperVipPayService quote = " << quoteInst2 << std::endl;
double quoteInst3 = userPayService.getByUserType("Vip")->quote(300);
std::cout << "VipPayService quote = " << quoteInst3 << std::endl;
此时我们已经可以仅给出会员类型及消费金额即得到相应的对象,并且直接调用统一的接口即可计算应付金额。