当前位置: 首页 > news >正文

C++设计模式:面向对象设计原则

目录

单一职责原则

开放 - 封闭原则

里氏替换原则

依赖倒置原则

接口隔离原则

迪米特法则

合成复用原则

总结


面向对象设计原则是指导开发者设计灵活、可维护、可扩展代码的基本准则。这些原则由 “四人组”(Gang of Four, GoF)在《设计模式:可复用面向对象软件的基础》中提出,其核心思想是 “高内聚、低耦合”。

单一职责原则

  • 定义:一个类应该只有一个引起它变化的原因,即一个类只负责一项职责。
  • 目的降低类的复杂度,提高代码的可读性和可维护性。
// 单一职责原则(SRP)示例// 反例:一个类承担多个职责
class BadUserHandler {
private:std::string username;
public:// 职责1:用户管理void setUsername(const std::string& name) {username = name;}// 职责2:日志记录(不属于用户管理职责)void logLogin() {std::ofstream file("log.txt", std::ios::app);file << username << " logged in at " << time(nullptr) << std::endl;}
};// 正例:职责分离
class UserManager {  // 仅负责用户管理
private:std::string username;
public:void setUsername(const std::string& name) {username = name;}std::string getUsername() const {return username;}
};class Logger {  // 仅负责日志记录
public:static void log(const std::string& message) {std::ofstream file("log.txt", std::ios::app);file << message << " at " << time(nullptr) << std::endl;}
};

开放 - 封闭原则

  • 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭
  • 目的通过扩展已有代码来应对变化,而非修改原有代码,避免引入新 bug。
  • 实现方式:依赖抽象(如接口或抽象类),通过子类继承或实现接口来扩展功能。
// 开放-封闭原则(OCP)示例// 反例:新增功能需要修改原有代码
class BadShapeDrawer {
public:void draw(const std::string& shape) {if (shape == "circle") {std::cout << "Drawing circle" << std::endl;} else if (shape == "square") {std::cout << "Drawing square" << std::endl;// 新增图形需要修改这里}}
};// 正例:对扩展开放,对修改关闭
class Shape {  // 抽象基类
public:virtual void draw() const = 0;  // 纯虚函数virtual ~Shape() = default;
};class Circle : public Shape {  // 扩展实现
public:void draw() const override {std::cout << "Drawing circle" << std::endl;}
};class Square : public Shape {  // 扩展实现
public:void draw() const override {std::cout << "Drawing square" << std::endl;}
};class ShapeDrawer {  // 无需修改即可支持新图形
public:void drawShape(const Shape& shape) {shape.draw();  // 依赖抽象}
};

里氏替换原则

  • 定义子类对象必须能够替换其基类对象,且程序逻辑不受影响(即 “子类可以扩展父类,但不能改变父类原有功能”)。
  • 目的:保证继承关系的合理性,防止子类破坏父类的预期行为
#include <iostream>// 抽象父类:定义所有形状的共同行为
class Shape {
public:virtual int getArea() = 0; // 核心行为:计算面积virtual ~Shape() = default; // 虚析构函数
};// 长方形子类
class Rectangle : public Shape {
private:int width;int height;
public:Rectangle(int w, int h) : width(w), height(h) {}void setWidth(int w) { width = w; }void setHeight(int h) { height = h; }int getArea() override { return width * height; } // 实现面积计算
};// 正方形子类
class Square : public Shape {
private:int side;
public:Square(int s) : side(s) {}void setSide(int s) { side = s; }int getArea() override { return side * side; } // 实现面积计算
};// 通用函数:接收Shape基类对象,计算并打印面积
void printArea(Shape& shape) {std::cout << "面积: " << shape.getArea() << std::endl;
}//Square和Rectangle作为Shape的子类,可以安全地替换Shape对象,且不会破坏程序的预期行为。
int main() {// 1. 创建Rectangle对象,用Shape&引用Rectangle rect(2, 3);Shape& shape1 = rect;printArea(shape1); // 输出:6(正确)// 2. 创建Square对象,用Shape&引用(替换父类对象)Square square(2);Shape& shape2 = square;printArea(shape2); // 输出:4(正确)// 3. 动态替换:在需要Shape的地方,可任意切换子类Shape* shapePtr;shapePtr = new Rectangle(3, 4);printArea(*shapePtr); // 输出:12(正确)delete shapePtr;shapePtr = new Square(3); // 替换为Square对象printArea(*shapePtr); // 输出:9(正确)delete shapePtr;return 0;
}

依赖倒置原则

  • 定义高层模块不依赖低层模块,两者都依赖抽象;抽象不依赖细节,细节依赖抽象。要依赖抽象,不要依赖具体实现”)。
  • 目的降低耦合度,提高系统的灵活性和可扩展性(高层模块可通过抽象调用不同的低层实现)。
#include <iostream>
#include <string>// 反例:违反依赖倒置原则
// 低层模块:MySQL数据库实现
class MySQLDatabase {
public:void save(const std::string& data) {std::cout << "保存数据到MySQL: " << data << std::endl;}
};// 高层模块:直接依赖具体的低层模块
class UserService {
private:// 问题:直接依赖具体的MySQLDatabaseMySQLDatabase db; public:void saveUser(const std::string& username) {db.save(username);}
};// 问题:如果要更换数据库为MongoDB,必须修改UserService
class MongoDatabase {
public:void save(const std::string& data) {std::cout << "保存数据到MongoDB: " << data << std::endl;}
};
// 此时需要修改UserService的实现才能使用MongoDatabase// 正例:遵循依赖倒置原则
// 抽象层:定义数据库操作接口(不依赖任何具体实现)
class Database {
public:virtual void save(const std::string& data) = 0; // 纯虚函数virtual ~Database() = default; // 虚析构函数
};// 低层模块:实现抽象接口
class MySQLDB : public Database {
public:void save(const std::string& data) override {std::cout << "保存数据到MySQL: " << data << std::endl;}
};class MongoDB : public Database {
public:void save(const std::string& data) override {std::cout << "保存数据到MongoDB: " << data << std::endl;}
};// 高层模块:只依赖抽象接口,不依赖具体实现
class UserManager {
private:Database* db; // 依赖抽象,而非具体类public:// 通过构造函数注入具体实现(依赖注入)UserManager(Database* database) : db(database) {}void saveUser(const std::string& username) {db->save(username); // 调用抽象接口,不关心具体是哪种数据库}
};// 使用示例
int main() {// 可以自由切换数据库,无需修改UserManagerDatabase* mysql = new MySQLDB();UserManager userMgr1(mysql);userMgr1.saveUser("Alice"); // 输出:保存数据到MySQL: AliceDatabase* mongo = new MongoDB();UserManager userMgr2(mongo);userMgr2.saveUser("Bob"); // 输出:保存数据到MongoDB: Bobdelete mysql;delete mongo;return 0;
}

接口隔离原则

  • 定义客户端不应该被迫依赖它不需要的接口。(即一个接口应只包含客户端需要的方法,避免 “胖接口”)。
  • 目的:减少接口冗余,防止客户端依赖无关方法,降低耦合。
#include <iostream>// 反例:违反接口隔离原则(胖接口)
// 一个包含所有功能的大接口
class Worker {
public:virtual void work() = 0;    // 工作virtual void eat() = 0;     // 吃饭virtual void sleep() = 0;   // 睡觉
};// 人类需要所有功能,没问题
class Human : public Worker {
public:void work() override { std::cout << "人类工作" << std::endl; }void eat() override { std::cout << "人类吃饭" << std::endl; }void sleep() override { std::cout << "人类睡觉" << std::endl; }
};// 机器人被迫实现不需要的方法(问题所在)
class Robot : public Worker {
public:void work() override { std::cout << "机器人工作" << std::endl; }// 机器人不需要吃饭和睡觉,但必须实现这些方法void eat() override { /* 空实现或抛出异常 */ }void sleep() override { /* 空实现或抛出异常 */ }
};// 正例:遵循接口隔离原则(拆分接口)
// 拆分后的小接口:每个接口只包含相关的方法
class Workable {
public:virtual void work() = 0;virtual ~Workable() = default;
};class Eatable {
public:virtual void eat() = 0;virtual ~Eatable() = default;
};class Sleepable {
public:virtual void sleep() = 0;virtual ~Sleepable() = default;
};// 人类需要所有接口,就实现所有接口
class HumanWorker : public Workable, public Eatable, public Sleepable {
public:void work() override { std::cout << "人类工作" << std::endl; }void eat() override { std::cout << "人类吃饭" << std::endl; }void sleep() override { std::cout << "人类睡觉" << std::endl; }
};// 机器人只需要工作接口,就只实现它
class RobotWorker : public Workable {
public:void work() override { std::cout << "机器人工作" << std::endl; }// 不需要实现eat和sleep,避免了冗余
};// 使用示例
int main() {// 人类可以做所有事HumanWorker human;human.work();human.eat();human.sleep();// 机器人只需要工作RobotWorker robot;robot.work();// 机器人没有eat和sleep方法,不会被误用return 0;
}

迪米特法则

  • 定义一个对象应该对其他对象保持最少的了解(只与直接朋友通信,不与 “陌生人” 交互)。
  • “直接朋友”:当前对象本身、方法参数、返回值、成员变量、局部变量。
  • 目的:减少对象之间的交互,降低系统复杂度。
// 反例:违反迪米特法则
namespace BadExample {// 引擎类class Engine {private:int power; // 功率public:Engine(int p) : power(p) {}int getPower() const { return power; }};// 汽车类class Car {private:Engine engine; // 汽车包含引擎public:Car(Engine e) : engine(e) {}// 问题:暴露了内部成员engine(朋友的朋友)Engine getEngine() const { return engine; }};// 司机类class Driver {public:// 司机直接访问了汽车的引擎(陌生人)void checkCar(const Car& car) {// 违反迪米特法则:Driver -> Car -> Engine(访问了朋友的朋友)std::cout << "引擎功率: " << car.getEngine().getPower() << std::endl;}};
}// 正例:遵循迪米特法则
namespace GoodExample {// 引擎类class Engine {private:int power;public:Engine(int p) : power(p) {}// 引擎只对直接朋友Car暴露必要接口int getPower() const { return power; }};// 汽车类class Car {private:Engine engine; // 内部成员,不对外暴露public:Car(Engine e) : engine(e) {}// 提供封装方法,隐藏内部细节int getEnginePower() const {// Car和Engine是直接朋友,交互合法return engine.getPower();}};// 司机类class Driver {public:// 司机只和直接朋友Car交互,不知道Engine的存在void checkCar(const Car& car) {// 符合迪米特法则:只访问直接朋友Car的方法std::cout << "引擎功率: " << car.getEnginePower() << std::endl;}};
}

合成复用原则

  • 定义优先使用组合(has-a)或聚合(contains-a)关系复用代码,而非继承(is-a)。
  • 目的:避免继承带来的强耦合(子类依赖父类实现),通过组合灵活替换组件。
#include <iostream>
#include <string>// 反例:过度使用继承(违反合成复用原则)
namespace BadExample {// 发动机类class Engine {protected:std::string type; // 发动机类型public:Engine(std::string t) : type(t) {}void start() {std::cout << type << "发动机启动" << std::endl;}};// 错误:汽车继承发动机("是一个"的关系不成立)class Car : public Engine {public:// 汽车被迫继承发动机的构造函数Car(std::string engineType) : Engine(engineType) {}void drive() {start(); // 复用发动机的启动功能std::cout << "汽车行驶中" << std::endl;}};
}// 正例:使用组合(遵循合成复用原则)
namespace GoodExample {// 发动机类class Engine {private:std::string type;public:Engine(std::string t) : type(t) {}void start() {std::cout << type << "发动机启动" << std::endl;}};// 正确:汽车包含发动机("有一个"的关系)class Car {private:// 组合发动机对象(核心:用成员变量持有,而非继承)Engine engine; public:// 通过构造函数注入发动机(灵活更换)Car(Engine e) : engine(e) {}void drive() {engine.start(); // 复用发动机的功能std::cout << "汽车行驶中" << std::endl;}};
}int main() {// 违反合成复用原则的情况BadExample::Car badCar("汽油");std::cout << "违反合成复用原则:" << std::endl;badCar.drive();// 问题:如果想换发动机为电动,必须修改Car类或新建子类// 遵循合成复用原则的情况GoodExample::Engine electricEngine("电动");GoodExample::Car goodCar(electricEngine); // 直接组合电动发动机std::cout << "\n遵循合成复用原则:" << std::endl;goodCar.drive();// 优势:换发动机只需传入新对象,无需修改Car类return 0;
}

总结

  • 单一职责原则要求类 / 接口专注于单一职责,避免功能混杂;
  • 开放 - 封闭原则强调对扩展开放、对修改关闭,通过抽象实现灵活扩展;
  • 里氏替换原则确保子类可安全替换父类而不破坏程序逻辑;
  • 依赖倒置原则主张依赖抽象而非具体实现,降低模块间耦合;
  • 接口隔离原则反对 “胖接口”,倡导拆分出专一接口以避免客户端依赖无关方法;
  • 迪米特法则要求对象仅与直接朋友交互,减少对其他对象的了解;
  • 合成复用原则优先通过组合 / 聚合复用代码,而非继承,以提高灵活性。

👉👈.

http://www.dtcms.com/a/335047.html

相关文章:

  • 肖臻《区块链技术与应用》第20-22讲 - 以太坊难度调整、权益证明和智能合约
  • Java:File类、递归、字符集、IO流体系及Commons-io框架
  • MySQL锁机制:悲观锁VS乐观锁详解
  • 关于“卷积“
  • 《智能体(Agent)速记指南》
  • Linux进程概念(四)环境地址变量
  • 算法第四十六天:动态规划part13(第九章)
  • STM32 软件I2C读写MPU6050
  • Redis入门与背景详解:构建高并发、高可用系统的关键基石
  • Linux系统编程练习、作业
  • Flink Stream API 源码走读 - 总结
  • 差分约束.
  • 腾讯混元大模型:实现3D打印产品生成的自动化平台
  • [Python 基础课程]继承
  • [Linux] RAID存储技术
  • 【102页PPT】电子行业数字化解决方案(附下载方式)
  • 容器化部署:用Docker封装机器翻译模型与服务详解
  • 服务器可以ping通,但部署的网站打不开
  • MyBatis 的 SQL 拦截器:原理、实现与实践
  • 基于Spring Boot的快递物流仓库管理系统 商品库存管理系统
  • OpenStack Neutron中的L2 Agent与L3 Agent:新手友好指南
  • Nginx蜘蛛请求智能分流:精准识别爬虫并转发SEO渲染服务
  • RemoteCtrl-初步的网络编程框架搭建
  • Linux 多线程:线程回收策略 线程间通信(互斥锁详解)
  • Easytier异地组网与Nginx反向代理
  • 昇腾AI自学Day2-- 深度学习基础工具与数学
  • 楼宇自控系统赋能建筑全维度管理,实现环境、安全与能耗全面监管
  • 计算分组内时间列的最大差值
  • 【AI论文】NextStep-1:迈向大规模连续令牌自回归图像生成
  • Warning: Unable to create personal MATLAB work folder:E:\绯荤粺榛樿\鏂囨。\MATLAB