做电工的有接单的网站吗贵州省住房城乡建设厅网站

张小明 2026/1/10 3:39:13
做电工的有接单的网站吗,贵州省住房城乡建设厅网站,做国际黄金看什么网站,国内视频培训网站建设各位编程爱好者、架构师们#xff0c;欢迎来到今天的技术讲座。今天我们将深入探讨一个在C面向对象设计中#xff0c;既强大又常被误解的设计模式——非虚接口#xff08;Non-Virtual Interface, NVI#xff09;模式。这个模式的核心理念是#xff1a;将虚函数声明为priva…各位编程爱好者、架构师们欢迎来到今天的技术讲座。今天我们将深入探讨一个在C面向对象设计中既强大又常被误解的设计模式——非虚接口Non-Virtual Interface, NVI模式。这个模式的核心理念是将虚函数声明为private或protected并提供public的非虚函数作为客户端与类交互的接口。为什么这种看似限制性的做法会成为一种被广泛推荐的优秀实践呢我们将围绕这个问题通过理论分析、代码示例和实际考量全面解析NVI模式的魅力与价值。虚函数的原始挑战缺乏控制与封装在深入NVI之前我们先回顾一下虚函数virtualfunction在C中的基本用法。虚函数是实现多态的关键它允许通过基类指针或引用调用派生类中重写的函数。这使得我们能够编写通用代码处理不同类型的对象。考虑一个简单的例子一个图形类我们希望计算其面积。#include iostream #include cmath // 基类Shape class Shape { public: // 这是一个公有虚函数 virtual double calculateArea() const { std::cout Shape::calculateArea() called. std::endl; return 0.0; // 默认实现或抛出异常 } virtual ~Shape() default; }; // 派生类Circle class Circle : public Shape { public: explicit Circle(double r) : radius(r) {} // 重写 calculateArea double calculateArea() const override { std::cout Circle::calculateArea() called. std::endl; return M_PI * radius * radius; } private: double radius; }; // 派生类Rectangle class Rectangle : public Shape { public: Rectangle(double w, double h) : width(w), height(h) {} // 重写 calculateArea double calculateArea() const override { std::cout Rectangle::calculateArea() called. std::endl; return width * height; } private: double width; double height; }; void printArea(const Shape s) { std::cout Area: s.calculateArea() std::endl; } int main() { Circle c(5.0); Rectangle r(4.0, 6.0); Shape s; printArea(c); printArea(r); printArea(s); // 调用基类的默认实现 return 0; }这段代码看起来很标准也符合多态的预期。然而这种直接将虚函数暴露为public的方式在某些场景下会引入一些问题缺乏前置条件Pre-conditions和后置条件Post-conditions的保证calculateArea()可能需要特定的对象状态才能正确执行例如图形的尺寸必须有效。如果直接暴露虚函数派生类可能会绕过这些检查。在计算面积之后我们可能需要进行一些通用操作例如日志记录、状态更新或错误处理。如果每个派生类都必须重复这些操作代码就会变得冗余且容易出错。违反Liskov替换原则LSP的风险LSP要求“子类型必须能够替换它们的基类型而不改变程序的正确性”。如果基类的public虚函数没有提供足够的上下文或保证派生类可能会以一种出乎意料的方式行为从而破坏基类的预期契约。基类对算法流程的控制力不足如果一个复杂操作由多个步骤组成其中一些步骤是通用的另一些是特定于派生类的。直接暴露虚函数使得基类难以强制执行这些通用步骤的顺序或存在性。增加派生类的负担和耦合派生类不仅要实现核心逻辑还要负责处理所有通用逻辑如参数验证、日志、资源清理等这增加了它们的复杂性。基类与派生类之间通过public virtual函数直接耦合基类对公共接口的任何修改都可能影响所有派生类。这些问题尤其是在构建大型、复杂的面向对象系统时会变得尤为突出。这就是NVI模式发挥作用的地方。引入NVI模式非虚接口的诞生NVI模式顾名思义是“Non-Virtual Interface”的缩写即“非虚接口”。它是一种特定应用了模板方法Template Method设计模式的C惯用法。其核心思想是提供一个public的非虚成员函数。这是客户端代码唯一会调用的接口。在这个public非虚函数内部调用一个private或protected的虚成员函数。这个虚函数才是派生类真正实现具体逻辑的地方。让我们用一个简单的例子来展示NVI模式的结构#include iostream #include cmath #include string // 基类Shape (使用NVI模式) class Shape { public: // 公有非虚接口这是客户端调用的入口 double getArea() const { // 1. 前置条件检查 (Pre-conditions) if (!isValid()) { std::cerr Error: Shape is not valid for area calculation. std::endl; return 0.0; // 或抛出异常 } // 2. 调用私有虚函数实现具体逻辑 double area calculateAreaImpl(); // 3. 后置条件操作 (Post-conditions) logAreaCalculation(area); return area; } virtual ~Shape() default; protected: // 也可以是 private取决于派生类是否需要直接访问 // 保护虚函数供派生类重写实现具体面积计算 virtual double calculateAreaImpl() const 0; // 纯虚函数强制派生类实现 // 保护函数用于前置条件检查派生类可以重写以提供更具体的检查 virtual bool isValid() const { // 默认实现所有形状都假定是有效的除非派生类另有规定 return true; } private: // 私有函数用于后置条件操作基类独有派生类不能修改 void logAreaCalculation(double area) const { std::cout DEBUG: Area calculated: area std::endl; } }; // 派生类Circle class Circle : public Shape { public: explicit Circle(double r) : radius(r) {} protected: // 实现基类的保护虚函数 double calculateAreaImpl() const override { std::cout Circle::calculateAreaImpl() called. std::endl; return M_PI * radius * radius; } // 重写 isValid 以提供更具体的检查 bool isValid() const override { return radius 0; } private: double radius; }; // 派生类Rectangle class Rectangle : public Shape { public: Rectangle(double w, double h) : width(w), height(h) {} protected: // 实现基类的保护虚函数 double calculateAreaImpl() const override { std::cout Rectangle::calculateAreaImpl() called. std::endl; return width * height; } // 重写 isValid 以提供更具体的检查 bool isValid() const override { return width 0 height 0; } private: double width; double height; }; void printShapeArea(const Shape s) { std::cout --- Calculating Area --- std::endl; std::cout Calculated Area: s.getArea() std::endl; std::cout ------------------------ std::endl; } int main() { Circle c1(5.0); Circle c2(-2.0); // 无效半径 Rectangle r1(4.0, 6.0); Rectangle r2(0.0, 5.0); // 无效宽度 printShapeArea(c1); printShapeArea(c2); // 应该触发错误信息 printShapeArea(r1); printShapeArea(r2); // 应该触发错误信息 // Shape s; // NVI模式下如果 calculateAreaImpl 是纯虚函数则不能实例化抽象基类 // 如果 calculateAreaImpl 有默认实现则可以实例化 Shape // 例如class Shape { protected: virtual double calculateAreaImpl() const { return 0.0; } }; return 0; }在这个NVI版本中getArea()是public的非虚接口它定义了计算面积的完整算法流程先检查有效性然后执行具体的计算最后记录结果。calculateAreaImpl()是protected的虚函数由派生类负责实现核心的计算逻辑。isValid()也是protected虚函数允许派生类提供自己的验证逻辑。logAreaCalculation()是private的非虚函数它封装了日志记录的细节派生类无法访问或修改。NVI模式的核心优势现在我们来详细剖析NVI模式带来的诸多好处。1. 强大的封装与流程控制NVI模式最显著的优势在于它赋予了基类对整个操作流程的强大控制力。public非虚函数例如上述的getArea()成为了一个模板方法它定义了一个算法的骨架将一些步骤延迟到子类中实现。代码体现// 在基类 Shape 中 double getArea() const { // 步骤1前置条件 (Pre-conditions) - 基类强制执行 if (!isValid()) { /* ... */ } // 步骤2核心操作 (Core Operation) - 委托给派生类实现 double area calculateAreaImpl(); // 步骤3后置条件 (Post-conditions) - 基类强制执行 logAreaCalculation(area); return area; }基类定义了“How to do it”的整体流程而不是“What to do”的具体细节。客户端调用getArea()时它总是按照isValid() - calculateAreaImpl() - logAreaCalculation()的顺序执行。派生类只能影响calculateAreaImpl()和isValid()的内部行为但不能改变它们的调用顺序、频率或是否被调用。强制执行前置和后置条件。无论哪个派生类在计算面积前都必须通过isValid()检查计算后都会被logAreaCalculation()记录。这极大地提高了代码的健壮性和一致性。隐藏实现细节。客户端只知道getArea()而calculateAreaImpl()等是内部实现细节对外不可见。这符合信息隐藏原则。2. 保证Liskov替换原则LSPLSP是面向对象设计的一个基石它要求派生类对象能够无缝替换基类对象而不会破坏程序的正确性。NVI模式通过以下方式帮助维护LSP统一的契约public非虚接口为所有派生类提供了统一的、稳定的行为契约。客户端通过这个契约与对象交互而不必关心具体的派生类型。强制执行不变性基类可以在public非虚接口中强制执行所有派生类都必须遵守的类不变性class invariants。例如确保isValid()通过才能进行计算。这意味着无论哪个Shape的派生类只要它通过getArea()方法被调用它就必须满足基类定义的所有前置条件并且其行为将受到基类定义的后置条件约束。防止误用如果虚函数是public的理论上派生类可以重写它并可能忽略一些必要的验证或清理步骤。NVI模式通过将这些步骤封装在非虚接口中防止了派生类“意外地”或“故意地”绕过它们。示例假设我们有一个DatabaseConnection基类它包含connect()、disconnect()和execute(query)等方法。connect()和disconnect()可能包含资源分配/释放、错误处理、认证等通用逻辑。class DatabaseConnection { public: bool open() { // NVI if (isConnected()) { /* already open */ return true; } // Pre-conditions: logging, setup std::cout Attempting to open connection... std::endl; bool success doOpen(); // Virtual hook // Post-conditions: error handling, state update if (success) { std::cout Connection opened successfully. std::endl; // connection_status true; } else { std::cerr Failed to open connection. std::endl; } return success; } void close() { // NVI if (!isConnected()) { /* already closed */ return; } // Pre-conditions: logging, checks std::cout Attempting to close connection... std::endl; doClose(); // Virtual hook // Post-conditions: resource release, state update std::cout Connection closed. std::endl; // connection_status false; } // ... other methods protected: virtual bool doOpen() 0; virtual void doClose() 0; virtual bool isConnected() const 0; }; class MySQLConnection : public DatabaseConnection { protected: bool doOpen() override { // Specific MySQL connection logic std::cout MySQL: Establishing connection... std::endl; return true; // Simulate success } void doClose() override { // Specific MySQL disconnection logic std::cout MySQL: Closing connection... std::endl; } bool isConnected() const override { return true; } // For simplicity }; // 使用 // DatabaseConnection* db new MySQLConnection(); // db-open(); // Calls NVI open, which then calls doOpen // db-close();通过NVI无论使用MySQLConnection还是PostgreSQLConnectionopen()和close()的整体流程包括日志、状态管理等都是一致的从而保证了LSP。3. 减少耦合与提高可维护性NVI模式有助于降低基类与派生类之间的耦合度。基类的公共接口稳定public非虚接口一旦确定通常会保持稳定。对该接口内部实现例如增加新的前置/后置条件的修改不会影响到派生类的接口因为派生类只关心private/protected的虚函数。派生类只关注核心逻辑派生类只需要实现基类通过private/protected虚函数暴露出的“变异点”而无需关心通用逻辑。这使得派生类的代码更简洁、更专注于其核心职责。集中化通用逻辑所有的通用逻辑如参数验证、日志、错误处理、资源清理等都集中在基类的public非虚函数中。这意味着如果这些通用逻辑需要修改只需修改一处所有派生类都会自动受益而无需逐一修改。对比直接public virtual和NVI的维护性特性public virtual函数NVI 模式通用逻辑位置散布在每个派生类的重写函数中或基类虚函数有默认实现集中在基类的public非虚接口中修改通用逻辑需要修改所有相关派生类容易遗漏和出错只需修改基类的public非虚接口所有派生类自动继承修改派生类职责可能需要处理核心逻辑和部分通用逻辑只需关注并实现核心的、特定的逻辑接口稳定性基类的public virtual接口可能因内部逻辑调整而影响基类的public非虚接口更稳定内部虚函数调整不影响外部契约防止误用派生类可能重写时忽略基类的契约或必要步骤基类强制执行流程派生类无法绕过前置/后置条件4. 增强安全性与鲁棒性通过NVI模式基类能够更好地保护其内部状态和行为。防止对象处于无效状态前置条件检查可以确保在执行核心操作之前对象处于一个有效的、可操作的状态。例如一个文件操作类在执行读写操作前可以强制检查文件是否已打开。统一错误处理后置条件可以统一处理操作完成后的错误或异常情况例如记录失败日志、回滚事务等。避免半初始化对象在某些复杂场景下对象可能需要多步初始化。NVI可以确保只有在所有初始化步骤都完成后才允许执行某些操作。考虑一个Transaction类它需要begin、commit或rollback。class Transaction { public: void performTransaction() { // NVI if (!isReadyForTransaction()) { std::cerr Error: Transaction not ready. std::endl; return; } beginTransaction(); // Calls hook try { doTransactionSteps(); // Calls hook commitTransaction(); // Calls hook } catch (const std::exception e) { std::cerr Transaction failed: e.what() std::endl; rollbackTransaction(); // Calls hook } } protected: virtual void beginTransaction() 0; virtual void doTransactionSteps() 0; virtual void commitTransaction() 0; virtual void rollbackTransaction() 0; virtual bool isReadyForTransaction() const { return true; } // Default };在这种情况下performTransaction()确保了事务的完整生命周期管理派生类只需实现具体的事务步骤而无需关心错误处理和事务的边界逻辑。5. 更好的可读性与自文档性NVI模式可以使代码的意图更加清晰。明确的职责分离public非虚函数清楚地表明了提供给客户端的稳定、高层次操作。private/protected虚函数则表明了需要子类实现或定制的内部细节。“Read-only”接口客户端通过public非虚接口与对象交互这些接口通常是行为的“入口点”。而虚函数作为内部实现就像是私有的方法不应该被客户端直接调用。自文档化基类的public非虚接口本身就描述了类提供的服务而其内部对虚函数的调用则描述了该服务的具体实现流程。NVI模式的几种变体NVI模式并不总是意味着虚函数必须是private。根据设计需求它也可以是protected。private virtual(严格NVI)用途当基类完全控制算法流程并且不希望派生类直接调用或以其他方式使用基类的虚函数实现时。派生类只能重写它而不能调用它。优点封装性最强基类对派生类的行为控制力最强。示例前面的Shape::calculateAreaImpl()。protected virtual(常见NVI)用途当基类定义了核心算法流程但允许派生类在重写虚函数时选择性地调用基类的实现例如在自己的逻辑之前或之后调用Base::doSomething()。优点提供了一定的灵活性允许派生类在需要时复用基类的部分逻辑。示例DatabaseConnection::doOpen()如果基类有部分打开逻辑派生类可能需要先调用基类版本。class Base { public: void operation() { // NVI // ... common pre-logic ... doOperation(); // calls protected virtual // ... common post-logic ... } protected: virtual void doOperation() { std::cout Base::doOperation std::endl; // Default implementation, or common partial implementation } }; class Derived : public Base { protected: void doOperation() override { std::cout Derived::doOperation - calling base first std::endl; Base::doOperation(); // Derived can choose to call bases implementation std::cout Derived::doOperation - then derived specific logic std::endl; } };选择private还是protected取决于基类对派生类行为的期望和控制程度。在大多数NVI的典型应用中protected提供了一个很好的平衡点因为它既允许基类定义模板方法又允许派生类在重写时有一定的自由度。如果NVI的目的是完全强制执行一个不可变的流程并且派生类不应该与基类的虚函数有任何交互除了重写那么private是更严格的选择。何时使用NVI模式NVI模式并非适用于所有情况但在以下场景中它能大放异彩需要强制执行前置或后置条件时当一个操作在执行核心逻辑之前或之后需要进行统一的验证、日志记录、资源管理或状态更新时。存在通用算法骨架但部分步骤需要派生类定制时模板方法模式这是NVI最经典的用例它允许基类定义整个流程而将可变的部分抽象为虚函数。希望提供一个稳定、受控的公共接口时当基类希望其公共接口对客户端保持高度稳定并且不希望派生类通过重写虚函数来改变公共接口的“契约”时。需要防止派生类误用或绕过关键逻辑时当直接暴露public virtual函数可能导致派生类实现不完整、不安全或不一致的行为时。提高代码可维护性和可扩展性时当通用逻辑需要集中管理以便于未来的修改和功能扩展时。何时不使用NVI模式NVI模式虽然强大但并非银弹。在某些情况下它可能是不必要的甚至会引入不必要的复杂性。当虚函数就是类的全部公共接口时如果一个基类本身就是一个纯接口例如所有成员函数都是public pure virtual那么它的目的就是定义一个契约客户端直接调用这些虚函数是完全合理的。例如class ILogger { // 接口类 public: virtual void logInfo(const std::string message) 0; virtual void logError(const std::string message) 0; virtual ~ILogger() default; }; class ConsoleLogger : public ILogger { public: void logInfo(const std::string message) override { std::cout [INFO] message std::endl; } void logError(const std::string message) override { std::cerr [ERROR] message std::endl; } };在这种情况下logInfo和logError本身就是客户端需要调用的核心功能没有额外的通用逻辑需要封装NVI模式就没有必要。当虚函数没有前置/后置条件并且没有通用逻辑需要封装时如果一个虚函数仅仅是实现某个特定功能没有任何需要基类统一管理的前置、后置或上下文逻辑那么将其直接声明为public virtual是完全可以接受的。过度使用NVI会增加不必要的间接性。当派生类需要完全自由地定义行为时如果设计意图是让派生类完全控制虚函数的行为包括其调用上下文和内部逻辑而不是受基类的模板方法约束那么直接的public virtual可能更合适。为了避免引入不必要的复杂性对于简单、功能单一的类和虚函数NVI模式可能会增加代码量和理解成本而带来的收益甚微。遵循KISSKeep It Simple, Stupid原则在确实需要时才引入这种模式。总结与展望NVI模式作为模板方法设计模式在C中的一个重要应用提供了一种优雅且强大的方式来控制多态行为。通过将核心算法的骨架定义在public非虚接口中并将可变部分委托给private或protected虚函数NVI模式极大地增强了面向对象设计的封装性、健壮性和可维护性。它使得基类能够强制执行前置/后置条件保证Liskov替换原则并集中管理通用逻辑从而构建出更加稳定和易于扩展的软件系统。在您的C设计实践中当您发现需要对多态操作的生命周期、状态或通用逻辑进行统一管理时NVI模式无疑是一个值得优先考虑的强大工具。理解并恰当运用NVI将是您迈向更高级C软件设计的重要一步。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

定制公司网站中国建设银行对公网站首页

向量数据库集成中的常见陷阱与性能优化策略 【免费下载链接】mindsdb mindsdb/mindsdb: 是一个基于 SQLite 数据库的分布式数据库管理系统,它支持多种数据存储方式,包括 SQL 和 NoSQL。适合用于构建分布式数据库管理系统,特别是对于需要轻量级…

张小明 2026/1/7 3:39:56 网站建设

淘宝网站建设规划书网站和系统的区别

你是否曾在Axure RP中迷失在英文菜单的海洋里?当设计灵感迸发时,却要花费宝贵时间在翻译软件和界面搜索之间切换?今天,我将带你彻底告别这种低效状态,用最简单直接的方法将Axure RP打造成完全中文的工作环境。 【免费下…

张小明 2026/1/7 3:39:57 网站建设

网站收录减少抖音代运营收费20万

PandasAI语义层:3步搞定多渠道广告归因,让营销决策更聪明 【免费下载链接】pandas-ai 该项目扩展了Pandas库的功能,添加了一些面向机器学习和人工智能的数据处理方法,方便AI工程师利用Pandas进行更高效的数据准备和分析。 项目地…

张小明 2026/1/9 17:06:17 网站建设

网站登录入口网页做啪啪网站

第一章:为什么顶尖团队都在用Dify 1.7.0做音频转换?真相令人震惊在人工智能与语音处理的交汇点,Dify 1.7.0 正悄然改写行业规则。其强大的音频转换能力不仅体现在高保真还原和低延迟处理上,更在于它将复杂模型封装为可编程接口&am…

张小明 2026/1/7 3:40:00 网站建设

潍坊网站建设优化营销式网站制作

APA第7版格式工具:快速提升学术写作效率的完整指南 【免费下载链接】APA-7th-Edition Microsoft Word XSD for generating APA 7th edition references 项目地址: https://gitcode.com/gh_mirrors/ap/APA-7th-Edition 在学术写作领域,规范的文献引…

张小明 2026/1/7 3:40:01 网站建设

国外不织布网站做的教具沈阳网络教育

Windows Vista 安全管理全解析 在当今数字化的时代,计算机安全至关重要。Windows Vista 作为一款广泛使用的操作系统,其安全管理涉及多个方面,包括文件权限、打印机共享、网络安全协议以及用户认证等。下面将详细介绍 Windows Vista 安全管理的相关内容。 文件权限管理 文…

张小明 2026/1/7 3:40:01 网站建设