C++ 学习记录(18) NVI

版权声明:如若转载,注明出处即可 https://blog.csdn.net/nishisiyuetian/article/details/82730961

NVI

non-virtual interfaces(NVI),非虚拟接口,私有虚函数。

令客户通过 public non-virtual 成员函数间接调用 private virtual 函数,是 Template mothod 设计模式的一种独特表现形式。这个 non-virtual 虚函数称为 virtual 函数的外覆器。(摘自《Effective C++》)

简单示例:

#include <iostream>
#include <vector>
#include <memory>
#include <assert.h>

class System {
private:
	std::vector<std::string> personNames;
public:
	void add(const std::string &name) {
		personNames.emplace_back(name);
	}
} ;

// 基类, 访问
// public non-virtual 接口 --> add 外覆器
// private virtual 接口    --> 强制派生类实现(缺省也可)
class Access {
public:
	Access(System *const _ptr) : ptr(_ptr) {}
	
	virtual ~Access() = default;

	void add(std::string name) {

		// 可以加入一些逻辑判断, 验证是否合法
		if(ptr == nullptr) return;

		// 可以设置适当场景, 例如加入线程锁控制等

		addPerson(name);     // 调用派生类的 virtual 函数, 真正的数据操作

		// 调用结束之后清理场景, 打日志等
		logCall(name);
	}
private:
	virtual void addPerson(std::string name) = 0 ;  

	static void logCall(const std::string &name) {
		std::cout << "name  " << name << "  is added to System" << std::endl ;
	}
private:
	System *ptr;
} ;

// 派生类, 学生访问
class studentAccess : public Access {
public:
	studentAccess(System *const _ptr) 
		: Access(_ptr), 
		  ptr(_ptr) {}
private:
	// 直接进行纯数据操作
	virtual void addPerson(std::string name) override {
		ptr->add(name);
	}
private:
	System *ptr;
} ;

int main () {
	std::shared_ptr<System> nameHolder = std::make_shared<System>();
	std::shared_ptr<Access> stuVisit = std::make_shared<studentAccess>(nameHolder.get());
	stuVisit->add("YHL");
	stuVisit->add("Fluence");
	return 0;
}

基类的 public non-virtual 外覆器可以确保在真正的 virtual 函数操作之前设定好适当场景,并在调用结束后清理场景。“事前工作”可以包括锁定互斥器(lock a mutex),制造运转日志记录项(log entry),验证 class 约束条件,验证函数先决条件等;“事后工作”可以包括解除互斥器(unlock a mutex),制造运转日志等。(摘自《Effective C++》)

特点:

(1)接口与实现分离,基类负责逻辑和事前事后工作,派生类专心负责数据操作。

(2)基类更加稳定。倘若派生类实现部分改动,只需要重新编译派生类所在文件即可,不影响基类的逻辑部分。基类掌控接口所有权——如果不是 NVI,采用的是普通的虚函数覆盖机制,基类中加入的逻辑判断一旦改动,在所有派生类中的逻辑部分都要改动(因为派生类的 virtual 虚函数会覆盖基类的 virtual 虚函数),这些文件都要重新编译。一定程度上,NVI 的基类集中了对逻辑的掌控,而且不能被子类覆盖,这就是“ public non-virtual ” 接口,不是虚函数的原因。而 virtual 接口是 private 的原因——防止越过“逻辑部分”直接调用 virtual 函数的情况,纯数据操作如果不加入一些逻辑或者线程保护容易出现 bug。

猜你喜欢

转载自blog.csdn.net/nishisiyuetian/article/details/82730961
今日推荐