24.策略模式实现

2026-01-22
2242 字 · 11 分钟

02.策略模式实现

策略模式的优缺点:

优点:

  1. 避免多重的条件判断

  2. 易扩展

缺点:

  1. 代码量增多

  2. 策略实现需要对外暴露

可以先看看如果没有使用策略模式,那我们在遇到扩展时是怎么处理的。就拿支付来举例吧。伪代码如下:

std::string doPay(const PayParam& payParam) {
// 先验
preCheck(payParam);
std::string result;
if (payParam.getPayChannelEnum() == PayChannelEnum::ALI) {
// 支付宝支付的一些校验和处理
result = doAli(payParam);
} else if (payParam.getPayChannelEnum() == PayChannelEnum::WECHAT) {
// 微信支付的一些校验和处理
result = doWechat(payParam);
} else {
// 其他情况的处理
result.clear();
}
// 结果处理
result = resultHandle(result);
return result;
}

这个时候如果又加上了银联支付,那么就得在这个if else的分支上进行修改,再加一个分支,如果我们的处理不只是这么简单的话,甚至还涉及到代码结构的调整,先验和结果处理调整,这不符合代码封装的开闭原则。而且也会改得很令人头痛。

1.策略实现

使用策略模式实现也是有好几种方法的,下面我就来讲讲我在真实场景中使用过的策略实现吧。

2.第一种

这种方式非常简单,简直就是上面的if else套了个封装的壳子而已。定义一个接口,然后接口有几个实现,在根据if else判断需要构造哪个实现。然后调用执行方法。

// 策略接口类,用于抽象支付的行为
class PayStrategy {
public:
virtual ~PayStrategy() = default;
virtual std::string doPay(const PayParam& payParam) = 0;
};
// 具体支付宝支付的实现
class AliPayStrategyImpl : public PayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "alipay";
}
};
// 具体微信支付的实现
class WechatPayStrategyImpl : public PayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "wechatpay";
}
};
// 调用支付的入口
class OrderPay {
public:
std::string doPay(const PayParam& payParam) {
// 先验
preCheck(payParam);
std::unique_ptr<PayStrategy> payStrategy;
if (payParam.getPayChannelEnum() == PayChannelEnum::ALI) {
payStrategy = std::make_unique<AliPayStrategyImpl>();
} else if (payParam.getPayChannelEnum() == PayChannelEnum::WECHAT) {
payStrategy = std::make_unique<WechatPayStrategyImpl>();
}
std::string result = payStrategy ? payStrategy->doPay(payParam) : "";
// 结果处理
result = resultHandle(result);
return result;
}
private:
void preCheck(const PayParam& payParam) {
}
std::string resultHandle(const std::string& result) {
// do something
return "result";
}
};

这种如果需要扩展的话,就是再加一个实现类,然后if else这里构造实现类的地方再增加一个分支,其实也没有完全解决掉对修改关闭的问题。当然如果将实例交给统一的容器管理的话又会方便很多。

3.第二种(推荐)

这种就比较有趣了,是使用枚举类来进行多策略的实现。下面我们就一起来看看吧。

// C++ 的枚举无法像 Java 那样让每个枚举常量各自实现方法,
// 惯用做法是枚举配合一个分派函数来模拟
enum class PayChannelEnum {
ALI,
WECHAT
};
std::string doPay(PayChannelEnum channel, const PayParam& payParam) {
switch (channel) {
case PayChannelEnum::ALI:
return "alipay";
case PayChannelEnum::WECHAT:
return "wechatpay";
default:
return "";
}
}

这种方式,代码看着很简洁,逻辑也很清晰,使用也很方便;但是有一个缺点,如果你需要用到外部注入的对象的话,这种方式就无法工作了。

4.第三种

接下来这种就需要依赖于一个统一的注册表了,它大致就是帮我们管理实现类的,类的构造和注入等。

// 策略接口类,用于抽象支付的行为。依然需要一个策略接口用来抽象支付的行为
class PayStrategy {
public:
virtual ~PayStrategy() = default;
virtual std::string doPay(const PayParam& payParam) = 0;
};
// 具体支付宝支付的实现
// 重点:注册时需要指定实例的名称
class AliPayStrategyImpl : public PayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "alipay";
}
};
// 具体微信支付的实现
// 重点:注册时需要指定实例的名称
class WechatPayStrategyImpl : public PayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "wechatpay";
}
};
// 调用支付的入口
class OrderPay {
public:
OrderPay() {
// 重点:构造时把各策略实现注册进 map,key 为实例名,value 为对应的实例
payStrategyMap["ALI"] = std::make_unique<AliPayStrategyImpl>();
payStrategyMap["WECHAT"] = std::make_unique<WechatPayStrategyImpl>();
}
std::string doPay(const PayParam& payParam) {
preCheck(payParam);
auto it = payStrategyMap.find(payParam.getPayChannelName());
std::string result = it != payStrategyMap.end() ? it->second->doPay(payParam) : "";
return resultHandle(result);
}
private:
void preCheck(const PayParam& payParam) {
// do something check
}
std::string resultHandle(const std::string& result) {
// do something
return "result";
}
std::unordered_map<std::string, std::unique_ptr<PayStrategy>> payStrategyMap;
};

当然这里我们是没有考虑那些异常情况,以及策略未找到之类的兜底或者处理等。实际编程是需要都考虑的。

可以看到在这段实现中我是标了几个重点。

  1. 首先是各自的实现类需要指定注册名称(策略对应的枚举名称),这是为了在注册表中查找的时候方便我们匹配用的

  2. 其次是在调用的入口OrderPay的构造函数里统一完成注册,这样才能把策略实现统一管理起来,调用时才能找到对应的策略实现

  3. payStrategyMap这个成员对应的是个map,key为实例名,value为对应的实例。这样在调用doPay时就可以根据传入的策略名找到实例然后执行具体方法。

缺点也很明显,需要指定实例名称,且注册时的key要与策略名保持一致。这样容易出现两个问题。

  1. 实例名称如果和其他实例冲突,后注册的会覆盖先注册的,那换个实例名称的话,调用方也得跟着改动,容易引起其他依赖的问题。
  2. 策略名和实例名称需要一一对应,如果有哪里大小写或者字符敲错了等问题,不太容易排查。

5.第四种(推荐)

这种目前是我常用的一种策略实现,不过呢,如果策略本身比较少的话,写这个轮子可能会觉得比较浪费,因为本身的架子代码就挺多的了。话不多说,show you the code。

// 很熟悉了吧?一定需要的一个策略接口
class PayStrategy {
public:
virtual ~PayStrategy() = default;
virtual std::string doPay(const PayParam& payParam) = 0;
};
// 重要。实现策略的一个抽象类。用于定义所有的策略实现的基类
class AbstractPayStrategy : public PayStrategy {
public:
// 重要。新定义一个策略实现类需要实现的策略。返回对应的枚举
virtual PayChannelEnum strategy() const = 0;
};
// 基础实现,注意,此处是实现顶上的策略接口。在调用入口地方也是注入此实现。
// 重要。多个实现时调用方只依赖这个统一入口,由它来分派具体策略
class PayStrategyImpl : public PayStrategy {
public:
// 构造器注入。然后转化成map存储。
explicit PayStrategyImpl(std::vector<std::unique_ptr<AbstractPayStrategy>> payStrategyList) {
for (auto& payStrategy : payStrategyList) {
payStrategies[payStrategy->strategy()] = std::move(payStrategy);
}
}
// 实际的支付接口都是调用此方法
std::string doPay(const PayParam& payParam) override {
return payStrategies[payParam.getPayChannelEnum()]->doPay(payParam);
}
private:
std::map<PayChannelEnum, std::unique_ptr<AbstractPayStrategy>> payStrategies;
};
// 注意这里的改动,此处是继承抽象类,并且实现返回策略枚举的接口
class AliPayStrategyImpl : public AbstractPayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "alipay";
}
PayChannelEnum strategy() const override {
return PayChannelEnum::ALI;
}
};
class WechatPayStrategyImpl : public AbstractPayStrategy {
public:
std::string doPay(const PayParam& payParam) override {
return "wechatpay";
}
PayChannelEnum strategy() const override {
return PayChannelEnum::WECHAT;
}
};

到此整体的架子就已经搭建好了,而具体的调用入口也是很简单的一个注入即可。

// 调用入口:构造时把所有策略实现一并"注入"进去
PayStrategyImpl payStrategy({std::make_unique<AliPayStrategyImpl>(),
std::make_unique<WechatPayStrategyImpl>()});

这种实现代码量很多,但是基本上模糊了选择策略的那部分逻辑,而且有个很重要的优点是只需要维护一个策略枚举,能很方便的将实现与策略枚举映射起来。

其中用到的知识点有:

构造器注入。PayStrategyImpl定义了一个vector参数的构造函数,实例化该类时把所有的策略实现统一传入进去即可 统一入口。当接口有多个实现时,调用方不直接依赖某个具体实现,而是只依赖PayStrategyImpl这个统一入口,由它来分派具体策略。 类图如下:

img

6.最后

关于策略模式的多种实现,到这里就算结束了。也是有写几个我平常有使用的策略模式实现,当然还有一些其实可以改造一下形成自己代码风格的实现方式,比如第三种,可以再定义一个全局的Map,在实现类构造时将自身注册到Map中(可以通过构造函数自注册的方式等),然后剩下的工作就水到渠成了。

每种实现其实都是有一些优缺点的,也并不一定适用于所有人和所有场景,希望大家能够找到符合自己代码风格的实现思路,也希望大家能够通过这一节对策略模式有一个清晰一点的认识,在实际工作中能够更简洁清晰的写自己的bug咯~。


Thanks for reading!

24.策略模式实现

2026-01-22
2242 字 · 11 分钟

已复制链接

评论区

目录