条款35:考虑虚函数以外的其它选择(Consider alternatives to virtual functions)

news/2025/1/6 6:36:25/

条款35:考虑虚函数以外的其它选择

1.1 提出问题

假设正在制作一款游戏,正在为游戏中的角色设计一个层次结构。

class GameCharacter {
public:virtual int healthValue() const; //返回角色的生命值;派生类可以重新定义它... // 
};

1.2 解决办法

让我们考虑一些其他的方法来实现同样的效果:

1.2.1 非虚接口

藉由非虚接口(non-virtual interface,NVI)实现 模板方法(Template Method)设计模式:

class GameCharacter {
public:
//healthValue负责包裹doHealthValueint healthValue() const // NVI,不允许派生类重新定义{ ... // 做一些“事前准备”工作(框架的一部分)int retVal = doHealthValue(); // 做真正的工作... // 做一些“事后清理”工作(框架的一部分)return retVal;}...
private:virtual int doHealthValue() const // 被隐藏在private里的虚函数,运行重写{... // 计算角色生命值的默认算法} 
}

如果让客户重写healthValue,则无法确保框架部分的执行。

1.2.2 函数指针

藉由函数指针(Function Pointer)实现策略(Strategy)设计模式:

class GameCharacter; // 前置声明
int defaultHealthCalc(const GameCharacter& gc);// 计算健康状况的默认算法
class GameCharacter {
public:typedef int (*HealthCalcFunc)(const GameCharacter&);explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc): healthFunc(hcf){}int healthValue() const{return healthFunc(*this);}...
private:HealthCalcFunc healthFunc;// 函数指针
};

相同角色类型的不同实例可以具有不同的生命值计算功能。

class EvilBadGuy : public GameCharacter {
public:explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc): GameCharacter(hcf)//初始化基类部分{...}...
};
//还可以通过添加成员函数,在运行时改变健康值的计算行为
int loseHealthQuickly(const GameCharacter&); // 健康值计算函数1
int loseHealthSlowly(const GameCharacter&);  // 健康值计算函数2
EvilBadGuy ebg1(loseHealthQuickly); // 相同类型的实例
EvilBadGuy ebg2(loseHealthSlowly); // 具有不同的健康值计算行为

:非成员函数,如果需要访问private成员,需要设置友元或添加接口,会破坏类的封装性。

1.2.3 std::function

藉由 std::function 实现策略(Strategy)设计模式:

class GameCharacter; // 和以前一样
int defaultHealthCalc(const GameCharacter& gc); // 和以前一样
class GameCharacter {
public:// HealthCalcFunc是“可调用的实体(callable entity)”// 它可以接受任何与GameCharacter兼容的东西,并返回任何与int兼容的东西typedef std::function<int(const GameCharacter&)> HealthCalcFunc;explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc): healthFunc(hcf){}int healthValue() const{return healthFunc(*this);}...
private:HealthCalcFunc healthFunc;
};

小小的一个变化,但结果是客户现在在指定生命值计算函数方面具有了惊人的灵活性:

short calcHealth(const GameCharacter&); // 健康值计算函数,返回类型不是intstruct HealthCalculator { // 函数对象:计算健康值int operator()(const GameCharacter&) const {...} 
};
class GameLevel {
public:float health(const GameCharacter&) const; // 成员函数:计算健康值... 
}; 
class EvilBadGuy : public GameCharacter { // 和以前一样...
};
class EyeCandyCharacter : public GameCharacter { // 另一种人物类型...  
}; EvilBadGuy ebg1(calcHealth);  
EyeCandyCharacter ecc1(HealthCalculator()); 
GameLevel currentLevel;
...
EvilBadGuy ebg2( std::bind( &GameLevel::health,  currentLevel, _1) 
);

1.2.4 古典的策略(Strategy)设计模式:

使健康值计算函数成为独立的层次结构的虚成员函数,这种方法提供了一种可能性,可以通过向HealthCalcFunc层次结构添加派生类来调整现有的健康值计算算法。
在这里插入图片描述
对应的代码框架:

class GameCharacter; // 前置声明
class HealthCalcFunc {
public:...virtual int calc(const GameCharacter& gc) const{...}...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public:explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc): pHealthCalc(phcf){}int healthValue() const{return pHealthCalc->calc(*this);}...
private:HealthCalcFunc* pHealthCalc;
};

1.3 总结

  1. virtual函数的替代方案包括NVI和策略设计模式的多种形式。NVI本身是一种特殊形式的Template Method设计模式。
  2. 将功能从成员函数移到class外部,带来一个缺点,非成员函数无法访问class的非public成员。
  3. function对象的行为就像一般函数指针。这样的对象可接纳“满足目标签名式”的所有可调用实体。

http://www.ppmy.cn/news/1560588.html

相关文章

【狂热算法篇】解锁数据潜能:探秘前沿 LIS 算法

嘿&#xff0c;各位编程爱好者们&#xff01;今天带来的 LIS 算法简直太赞啦 无论你是刚入门的小白&#xff0c;还是经验丰富的大神&#xff0c;都能从这里找到算法的奇妙之处哦&#xff01;这里不仅有清晰易懂的 C 代码实现&#xff0c;还有超详细的算法讲解&#xff0c;让你轻…

redis7基础篇2 redis的主从模式1

目录 一 主从模式 1.1 主从复制的作用 1.2 配置常用命令 1.3 主从复制常见问题 1.4 主从复制的缺点 1.5 redis主从复制原理 二 redis主从复制的搭建流程 2.1 注意事项 2.2 redis的主从复制架构图 2.3 以6379.conf配置文件配置为例 2.4 以6380.conf配置文件配置为例 …

数论问题22

题、证明&#xff0c;方程x-yz1具有无限多个正整数解(x&#xff0c;y&#xff0c;z)&#xff0c;其中x&#xff0c;y&#xff0c;z两两不同&#xff0c;且任意两个之积都被第三个整除。 分析与证明:假设方程的正整数解两两不同&#xff0c;并且任意两个之积都被第三个整除。即 …

React知识盲点——组件通信、性能优化、高级功能详解(大纲)

组件通信 React 组件通信详解 在 React 中&#xff0c;组件通信是一个核心概念&#xff0c;主要指的是如何让不同的组件共享和传递数据。React 提供了多种机制来实现组件间的数据传递和状态共享。以下是几种常见的组件通信方式&#xff0c;包括&#xff1a;父子组件通信&…

计算机网络 (22)网际协议IP

一、IP协议的基本定义 IP协议是Internet Protocol的缩写&#xff0c;即因特网协议。它是TCP/IP协议簇中最核心的协议&#xff0c;负责在网络中传送数据包&#xff0c;并提供寻址和路由功能。IP协议为每个连接在因特网上的主机&#xff08;或路由器&#xff09;分配一个唯一的IP…

flink cdc oceanbase(binlog模式)

接上文&#xff1a;一文说清flink从编码到部署上线 环境&#xff1a;①操作系统&#xff1a;阿里龙蜥 7.9&#xff08;平替CentOS7.9&#xff09;&#xff1b;②CPU&#xff1a;x86&#xff1b;③用户&#xff1a;root。 预研初衷&#xff1a;现在很多项目有国产化的要求&#…

蓝桥杯JAVA--003

需求 2.代码 public class RegularExpressionMatching {public boolean isMatch(String s, String p) {if (p.isEmpty()) {return s.isEmpty();}boolean firstMatch !s.isEmpty() && (s.charAt(0) p.charAt(0) || p.charAt(0) .);if (p.length() > 2 && p…

三层交换机的原理详解

三层交换机的工作原理 三层交换机是结合了二层交换机和路由器功能的设备。在现代网络中&#xff0c;三层交换机主要评估局域网&#xff08;LAN&#xff09;中&#xff0c;以便快速进行流量转发、VLAN间的路由、以及跨子网的通信。 三层交换机的关键特点是能够在二层交换的基础…