面向对象编程的四大核心原则


1. 封装


核心思想:将数据(属性)和操作数据的方法(函数)捆绑在一起,并隐藏对象的内部实现细节。对象只对外提供一个可控的访问接口。

封装的详细分析和使用细节

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:
    string accountHolder;
    string accountNumber;
    double balance;  // 私有数据,外部无法直接访问

public:
    // 构造函数
    BankAccount(string holder, string accNumber, double initialBalance = 0) {
        accountHolder = holder;
        accountNumber = accNumber;
        balance = initialBalance;
    }
    
    // 公开接口 - 存款
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            cout << "存款成功,当前余额: " << balance << endl;
        } else {
            cout << "存款金额必须大于0" << endl;
        }
    }
    
    // 公开接口 - 取款
    void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            cout << "取款成功,当前余额: " << balance << endl;
        } else {
            cout << "取款金额无效或余额不足" << endl;
        }
    }
    
    // 公开接口 - 获取余额(只读)
    double getBalance() const {
        return balance;
    }
    
    // 公开接口 - 获取账户信息
    string getAccountInfo() const {
        return "账户持有人: " + accountHolder + ", 账户号码: " + accountNumber;
    }
};

int main() {
    BankAccount account("张三", "123456789", 1000);
    
    account.deposit(500);     // ✅ 通过公开接口操作
    account.withdraw(200);    // ✅ 通过公开接口操作
    
    // account.balance = 10000;  // ❌ 编译错误:无法访问私有成员
    cout << "当前余额: " << account.getBalance() << endl;  // ✅ 通过getter访问
    
    cout << account.getAccountInfo() << endl;
    
    return 0;
}

好处:

1.提高安全性:防止外部代码随意修改内部数据,只能通过你提供的方法来修改,你可以在方法中添加验证逻辑。

2.降低复杂度:使用者无需关心内部实现,只需关注接口。

3.易于维护:内部实现可以自由更改,只要对外接口不变,就不会影响其他代码。

2. 抽象


核心思想:只向用户展示必要的、关键的信息,而隐藏不必要的实现细节。它是封装的更高层次,通过抽象类和接口来实现。

#include <iostream>
#include <string>
using namespace std;

// 抽象类 - 包含纯虚函数
class Vehicle {
public:
    // 纯虚函数 - 接口声明,没有实现
    virtual void startEngine() = 0;
    virtual void stopEngine() = 0;
    virtual double getFuelLevel() const = 0;
    virtual ~Vehicle() {}  // 虚析构函数
};

// 具体实现类
class Car : public Vehicle {
private:
    string engineStatus;
    double fuelLevel;
    bool sparkPlugsOk;
    
    // 私有方法 - 隐藏实现细节
    bool checkSparkPlugs() {
        return sparkPlugsOk;
    }
    
public:
    Car() : engineStatus("off"), fuelLevel(100), sparkPlugsOk(true) {}
    
    void startEngine() override {
        if (checkSparkPlugs() && fuelLevel > 0) {
            engineStatus = "on";
            fuelLevel -= 5;
            cout << "🚗 汽车引擎已启动" << endl;
        } else {
            cout << "无法启动引擎" << endl;
        }
    }
    
    void stopEngine() override {
        engineStatus = "off";
        cout << "🚗 汽车引擎已关闭" << endl;
    }
    
    double getFuelLevel() const override {
        return fuelLevel;
    }
};

class Motorcycle : public Vehicle {
private:
    bool engineRunning;
    double fuel;
    
public:
    Motorcycle() : engineRunning(false), fuel(80) {}
    
    void startEngine() override {
        engineRunning = true;
        fuel -= 3;
        cout << "🏍️ 摩托车引擎已启动" << endl;
    }
    
    void stopEngine() override {
        engineRunning = false;
        cout << "🏍️ 摩托车引擎已关闭" << endl;
    }
    
    double getFuelLevel() const override {
        return fuel;
    }
};

int main() {
    // Vehicle v;  // ❌ 错误:不能实例化抽象类
    
    Vehicle* car = new Car();
    Vehicle* motorcycle = new Motorcycle();
    
    car->startEngine();           // ✅ 用户只知道简单接口
    cout << "汽车油量: " << car->getFuelLevel() << "%" << endl;
    
    motorcycle->startEngine();    // ✅ 统一接口,不同实现
    cout << "摩托车油量: " << motorcycle->getFuelLevel() << "%" << endl;
    
    delete car;
    delete motorcycle;
    
    return 0;
}

好处:

1.简化复杂性:让程序员可以专注于高级逻辑,而不是底层实现。

2.降低耦合:代码模块之间只通过抽象的接口交互,减少依赖。

封装 vs. 抽象:

封装是关于“如何隐藏数据和控制访问”。

抽象是关于“如何隐藏实现细节并提供简化接口”。可以认为抽象是通过封装等手段实现的更高层次的设计思想。

3. 继承


核心思想:允许一个类(子类/派生类)基于另一个类(父类/基类)来创建。子类会自动获得父类的属性和方法,并可以添加自己特有的属性和方法。

#include <iostream>
#include <string>
using namespace std;

// 基类(父类)
class Animal {
protected:
    string name;
    int age;
    
public:
    Animal(string n, int a) : name(n), age(a) {}
    
    // 虚函数 - 允许子类重写
    virtual void eat() {
        cout << name << " 正在吃东西" << endl;
    }
    
    virtual void sleep() {
        cout << name << " 正在睡觉" << endl;
    }
    
    virtual void makeSound() {
        cout << "动物发出声音" << endl;
    }
    
    virtual ~Animal() {}  // 虚析构函数
};

// 派生类(子类)
class Dog : public Animal {
private:
    string breed;
    
public:
    Dog(string n, int a, string b) : Animal(n, a), breed(b) {}
    
    // 重写基类方法
    void makeSound() override {
        cout << name << " 汪汪叫!" << endl;
    }
    
    // 子类特有方法
    void fetch() {
        cout << name << " 正在接飞盘" << endl;
    }
    
    void displayInfo() {
        cout << name << " 是一只 " << breed << ",今年 " << age << " 岁" << endl;
    }
};

class Cat : public Animal {
private:
    string color;
    
public:
    Cat(string n, int a, string c) : Animal(n, a), color(c) {}
    
    void makeSound() override {
        cout << name << " 喵喵叫!" << endl;
    }
    
    void climbTree() {
        cout << name << " 正在爬树" << endl;
    }
    
    void displayInfo() {
        cout << name << " 是一只 " << color << "的猫,今年 " << age << " 岁" << endl;
    }
};

int main() {
    Dog dog("旺财", 3, "金毛");
    Cat cat("咪咪", 2, "白色");
    
    dog.eat();          // ✅ 继承自Animal
    dog.makeSound();    // ✅ 重写的方法
    dog.fetch();        // ✅ 子类特有方法
    dog.displayInfo();
    
    cout << endl;
    
    cat.sleep();        // ✅ 继承自Animal
    cat.makeSound();    // ✅ 重写的方法  
    cat.climbTree();    // ✅ 子类特有方法
    cat.displayInfo();
    
    return 0;
}

好处:

1.代码复用:子类可以直接复用父类的代码,无需重写。

2.建立类之间的层次关系:使代码结构更清晰,更符合现实世界的逻辑。

4. 多态


核心思想:意为“多种形态”。它允许使用一个统一的接口来操作不同的底层对象。具体来说,同一个方法在不同的对象上会有不同的行为,通常通过继承和方法重写来实现。

多态的使用细节

#include <iostream>
#include <vector>
#include <memory>  // 智能指针
using namespace std;

// 使用前面定义的Animal, Dog, Cat类

void animalConcert(const vector<Animal*>& animals) {
    cout << "=== 动物音乐会 ===" << endl;
    for (const auto& animal : animals) {
        animal->makeSound();  // 多态:同一接口,不同行为
    }
}

void takeCareOfAnimal(Animal* animal) {
    cout << "正在照顾动物..." << endl;
    animal->eat();
    animal->sleep();
    animal->makeSound();
    cout << "---" << endl;
}

int main() {
    vector<Animal*> animals;
    
    // 创建不同种类的动物
    animals.push_back(new Dog("旺财", 3, "金毛"));
    animals.push_back(new Cat("咪咪", 2, "白色"));
    animals.push_back(new Dog("小黑", 4, "拉布拉多"));
    
    // 多态演示
    animalConcert(animals);
    
    cout << "\n=== 动物护理 ===" << endl;
    for (auto animal : animals) {
        takeCareOfAnimal(animal);
    }
    
    // 清理内存
    for (auto animal : animals) {
        delete animal;
    }
    
    return 0;
}

好处:

1.接口统一:编写更通用、更灵活的代码。

2.可扩展性:添加新的子类(如 Bird)时,无需修改处理 Animal 的通用代码。

其他非常重要的关联原则和概念


除了四大支柱,在现代软件工程中,以下原则也至关重要,它们指导我们如何更好地使用OOP。

1. 组合优于继承


核心思想:尽量使用组合(包含关系)而不是继承(是一个关系)来复用代码。

组合:一个类包含另一个类的对象作为其成员变量。好比“有一个”的关系,例如 Car 类“有一个” Engine(引擎)对象。

继承会带来很强的耦合性,父类的任何改动都可能影响所有子类,这会使设计变得僵化。组合更加灵活,可以在运行时动态改变行为,并且降低了类之间的依赖。

设计原则:如果关系是“有一个”(如汽车有引擎),用组合;如果是“是一个”(如狗是动物),才考虑用继承。

#include <iostream>
#include <memory>
using namespace std;

// 使用组合而不是继承
class Engine {
public:
    void start() {
        cout << "燃油引擎启动" << endl;
    }
    
    void stop() {
        cout << "燃油引擎关闭" << endl;
    }
};

class ElectricMotor {
public:
    void start() {
        cout << "电动马达启动" << endl;
    }
    
    void stop() {
        cout << "电动马达关闭" << endl;
    }
};

class Battery {
private:
    double chargeLevel;
    
public:
    Battery() : chargeLevel(100) {}
    
    void charge() {
        chargeLevel = 100;
        cout << "电池已充满" << endl;
    }
    
    double getChargeLevel() const {
        return chargeLevel;
    }
};

// 通过组合构建汽车
class Car {
private:
    // 组合:拥有其他类的对象
    unique_ptr<Engine> engine;
    unique_ptr<Battery> battery;
    string carType;
    
public:
    // 构造函数注入不同的动力系统
    Car(unique_ptr<Engine> eng, unique_ptr<Battery> bat, string type) 
        : engine(move(eng)), battery(move(bat)), carType(type) {}
    
    void start() {
        cout << carType << "启动: ";
        if (engine) {
            engine->start();
        }
    }
    
    void stop() {
        cout << carType << "关闭: ";
        if (engine) {
            engine->stop();
        }
    }
    
    void checkBattery() {
        if (battery) {
            cout << "电池电量: " << battery->getChargeLevel() << "%" << endl;
        }
    }
};

int main() {
    // 创建不同类型的汽车(通过组合)
    auto gasCar = make_unique<Car>(
        make_unique<Engine>(), 
        make_unique<Battery>(), 
        "燃油车"
    );
    
    auto hybridCar = make_unique<Car>(
        make_unique<Engine>(),
        make_unique<Battery>(),
        "混合动力车"
    );
    
    gasCar->start();
    gasCar->checkBattery();
    
    cout << endl;
    
    hybridCar->start();
    hybridCar->checkBattery();
    
    return 0;
}

2. SOLID 原则

S - 单一职责原则:一个类应该只有一个引起它变化的原因。即一个类只负责一项功能。

O - 开放封闭原则:软件实体(类、模块、函数)应该对扩展开放,对修改关闭。意思是,当需要添加新功能时,应该通过扩展(如继承、多态)来实现,而不是修改已有的代码。

L - 里氏替换原则:子类必须能够替换掉它们的父类,而不破坏程序。这是多态能正确工作的基础。确保继承关系是真正合理的“是一个”关系。

I - 接口隔离原则:不应该强迫客户依赖于它们不用的方法。即使用多个专门的接口,而不是一个庞大臃肿的总接口。

D - 依赖倒置原则:

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

简单说:要依赖于接口(抽象),而不是具体的实现。

#include <iostream>
#include <string>
using namespace std;

// 违反单一职责原则的类(承担太多责任)
class EmployeeBad {
private:
    string name;
    double salary;
    
public:
    EmployeeBad(string n, double s) : name(n), salary(s) {}
    
    // 计算工资 - 财务职责
    double calculatePay() {
        return salary * 0.9;  // 扣除税费
    }
    
    // 数据库操作 - 数据持久化职责
    void saveToDatabase() {
        cout << "将 " << name << " 的信息保存到数据库" << endl;
    }
    
    // 生成报告 - 报告生成职责
    void generateReport() {
        cout << "为 " << name << " 生成工作报告" << endl;
    }
};

// 遵循单一职责原则的重构
class Employee {
private:
    string name;
    double salary;
    
public:
    Employee(string n, double s) : name(n), salary(s) {}
    
    string getName() const { return name; }
    double getSalary() const { return salary; }
};

// 专门的工资计算类
class PayCalculator {
public:
    double calculatePay(const Employee& emp) const {
        return emp.getSalary() * 0.9;  // 扣除税费
    }
};

// 专门的数据访问类
class EmployeeRepository {
public:
    void save(const Employee& emp) const {
        cout << "将 " << emp.getName() << " 的信息保存到数据库" << endl;
    }
};

// 专门的报告生成类
class ReportGenerator {
public:
    void generateReport(const Employee& emp) const {
        cout << "为 " << emp.getName() << " 生成工作报告" << endl;
    }
};

int main() {
    Employee emp("李四", 5000);
    PayCalculator payCalc;
    EmployeeRepository repository;
    ReportGenerator reportGen;
    
    cout << "应发工资: " << payCalc.calculatePay(emp) << endl;
    repository.save(emp);
    reportGen.generateReport(emp);
    
    return 0;
}

3.接口隔离原则

#include <iostream>
using namespace std;

// 庞大的接口 - 违反接口隔离原则
class IWorker {
public:
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual void code() = 0;
    virtual void design() = 0;
    virtual void test() = 0;
    virtual ~IWorker() {}
};

// 遵循接口隔离原则:拆分成专门的接口
class IWorkable {
public:
    virtual void work() = 0;
    virtual ~IWorkable() {}
};

class IEatable {
public:
    virtual void eat() = 0;
    virtual ~IEatable() {}
};

class ICodeable {
public:
    virtual void code() = 0;
    virtual ~ICodeable() {}
};

class IDesignable {
public:
    virtual void design() = 0;
    virtual ~IDesignable() {}
};

// 具体类只需要实现需要的接口
class Programmer : public IWorkable, public ICodeable {
public:
    void work() override {
        cout << "程序员开始工作" << endl;
    }
    
    void code() override {
        cout << "程序员在写代码" << endl;
    }
};

class Designer : public IWorkable, public IDesignable {
public:
    void work() override {
        cout << "设计师开始工作" << endl;
    }
    
    void design() override {
        cout << "设计师在做设计" << endl;
    }
};

int main() {
    Programmer prog;
    Designer designer;
    
    prog.work();
    prog.code();
    // prog.design();  // ❌ 程序员不需要设计方法
    
    cout << endl;
    
    designer.work();
    designer.design();
    // designer.code();  // ❌ 设计师不需要编码方法
    
    return 0;
}

设计规范

1.代码风格:

遵循一致的命名规范(如驼峰命名法、下划线命名法)。

使用有意义的变量名、函数名和类名。

保持函数短小,功能单一。

限制行的长度(通常80或120字符)。

一致地使用缩进和空格。

// 类型名 - PascalCase
class MyClass {};
struct MyStruct {};
using MyAlias = int;

// 变量名 - snake_case
int student_count;
std::string file_name;

// 常量 - kPascalCase或UPPER_CASE
const int kMaxSize = 100;
constexpr double PI = 3.14159;

// 函数名 - PascalCase或snake_case
void CalculateTotal();  // 推荐
void calculate_total(); // 也可接受

2.头文件管理:

头文件应包含防止重复包含的守卫(#pragma once 或 #ifndef)。

头文件只包含必要的声明,避免包含不必要的头文件,使用前向声明减少依赖。

头文件应当自包含(即包含它所需要的所有头文件)。

如何正确使用头文件引用和前向声明

// my_class.h
#ifndef MY_PROJECT_MY_CLASS_H
#define MY_PROJECT_MY_CLASS_H

#include <vector>
#include <string>

namespace my_project {

class MyClass {
public:
    explicit MyClass(int initial_value);
    void process_data();
    int get_result() const { return result_; }

private:
    int result_;
    std::vector<int> data_;
};

} // namespace my_project

#endif // MY_PROJECT_MY_CLASS_H

3.类设计:

明确类的职责,遵循单一职责原则。

使用访问修饰符(public, protected, private)严格控制成员的可访问性。

优先使用组合 over 继承。

如果类包含动态分配的资源,应遵循Rule of Three/Five/Zero(提供拷贝构造函数、拷贝赋值运算符、析构函数,或使用智能指针避免手动管理)。

4.函数设计:

函数参数:优先使用const引用传递大型对象,避免值传递;输出参数使用指针或引用。

函数重载:当函数执行相似操作但参数不同时,使用重载。

默认参数:谨慎使用,避免与重载混淆。

规范的函数设计模式

class Container {
public:
    // 明确的行为
    void add_item(const Item& item);
    void remove_item(int id);
    bool contains(int id) const;
    size_t size() const noexcept;
    
    // 禁止拷贝但允许移动
    Container(const Container&) = delete;
    Container& operator=(const Container&) = delete;
    Container(Container&&) noexcept = default;
    Container& operator=(Container&&) noexcept = default;
    
private:
    std::vector<Item> items_;
};

5.内存管理:

优先使用智能指针(unique_ptr, shared_ptr, weak_ptr)管理动态内存,避免手动new/delete。

智能指针的介绍和用法细节

// 优先使用智能指针
auto ptr = std::make_unique<MyClass>();
auto shared = std::make_shared<MyClass>();

// 避免裸new/delete
// 不好的做法: MyClass* obj = new MyClass();
// 好的做法: auto obj = std::make_unique<MyClass>();

6.常量正确性:

使用const修饰不改变的对象和成员函数,提高代码的可读性和安全性。

class MyClass {
public:
    // const成员函数,不修改对象状态
    int get_value() const { return value_; }
    
    // 非const成员函数
    void set_value(int v) { value_ = v; }
    
private:
    int value_;
};

7.使用auto和类型推导

auto的介绍和使用

// 好的用法
auto iter = container.begin();
const auto& item = get_item();
auto result = calculate_complex_value();

// 避免过度使用
auto x = 42;  // 可以,类型明确
auto y = get_something();  // 需要看函数签名

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐