第三章:面向对象与设计模式
第一课:深入理解OOP
面向对象编程(OOP)是一种编程范式,它将程序结构视为由对象组成,促进了代码的重用性和可维护性。在这一课中,我们将深入分析OOP的四个基本特性:封装、继承、多态和抽象,并提供相应的示例与实践。
1. OOP基本特性
1.1 封装
封装是OOP的核心概念之一,它指的是将对象的状态(属性)和行为(方法)组合在一起,同时隐藏内部实现细节,只暴露必要的接口。这使得对象可以保护自己的数据不被外部干扰,从而提高了代码的安全性和稳定性。
示例:
class BankAccount {
private:
double balance; // 账户余额
public:
BankAccount(double initial_balance) : balance(initial_balance) {}
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
double getBalance() const {
return balance;
}
};
在上面的示例中,balance
属性被声明为私有(private
),外部代码无法直接访问。只有通过公共方法(deposit
、withdraw
和getBalance
),才能修改或获取余额。
1.2 继承
继承允许一个类从另一个类派生,从而获得其属性和行为。这种机制支持代码重用和逻辑结构的组织,使得程序设计更加灵活。
示例:
class SavingsAccount : public BankAccount {
private:
double interestRate; // 利率
public:
SavingsAccount(double initial_balance, double rate)
: BankAccount(initial_balance), interestRate(rate) {}
void applyInterest() {
deposit(getBalance() * interestRate);
}
};
在这个例子中,SavingsAccount
类继承自BankAccount
类,得到了其所有的属性和方法,并增加了一个新的方法applyInterest
,用于计算利息。
1.3 多态
多态允许对象以不同的形式表现。这意味着可以使用同一接口来处理不同类型的对象。这通常通过虚函数实现,使得程序可以在运行时选择调用哪个函数。
示例:
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() const override {
// 画圆的逻辑
}
};
class Rectangle : public Shape {
public:
void draw() const override {
// 画矩形的逻辑
}
};
void render(const Shape& shape) {
shape.draw(); // 动态调用
}
在这个示例中,Shape
是一个基类,定义了一个纯虚函数draw
,Circle
和Rectangle
类实现了这个函数。通过传递Shape
引用,render
函数能够根据实际对象类型动态调用相应的draw
方法。
1.4 抽象
抽象是指通过提取对象的共同特征来创建类。抽象类通常包含纯虚函数,无法实例化。它们提供了一个模板,其他类可以从中继承并实现特定的功能。
示例:
class Animal {
public:
virtual void makeSound() const = 0; // 抽象方法
};
class Dog : public Animal {
public:
void makeSound() const override {
// 狗叫的逻辑
}
};
class Cat : public Animal {
public:
void makeSound() const override {
// 猫叫的逻辑
}
};
在这个例子中,Animal
是一个抽象类,定义了一个纯虚函数makeSound
。Dog
和Cat
类实现了这个函数,表示它们各自的叫声。
2. 类的设计原则与实际案例
设计原则帮助开发者编写可维护、可扩展和可重用的代码。以下是几个常用的设计原则:
2.1 单一职责原则(SRP)
一个类应该只有一个单一的职责,所有的功能都应该围绕这个职责展开。这可以减少类的复杂性,便于维护和修改。
示例:
class User {
public:
void login() {
// 登录逻辑
}
};
class UserNotifier {
public:
void notifyUser() {
// 通知用户逻辑
}
};
在这个示例中,User
类负责用户登录,而UserNotifier
类负责用户通知。两个类各司其职,符合单一职责原则。
2.2 开放封闭原则(OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着可以通过添加新代码而不是修改现有代码来实现新功能。
示例:
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
double area() const override {
// 计算圆的面积
}
};
class Rectangle : public Shape {
public:
double area() const override {
// 计算矩形的面积
}
};
// 新增多边形类
class Polygon : public Shape {
public:
double area() const override {
// 计算多边形的面积
}
};
在这个例子中,新增的Polygon
类并没有修改现有的代码,而是通过继承Shape
类实现新的功能,符合开放封闭原则。
2.3 里氏替换原则(LSP)
子类对象应该能够替换父类对象,而不会影响程序的正确性。这意味着子类必须符合父类的约定。
示例:
void drawShape(const Shape& shape) {
shape.draw(); // 可以接受任何形状的子类
}
确保Circle
和Rectangle
等子类都能正确执行父类的方法,不会引入错误。
2.4 接口隔离原则(ISP)
不应强迫客户依赖于他们不使用的接口。接口应该细化为特定的、功能单一的接口。
示例:
class IShape {
public:
virtual void draw() const = 0;
};
class IColor {
public:
virtual void fill() const = 0;
};
class Circle : public IShape, public IColor {
public:
void draw() const override {
// 画圆的逻辑
}
void fill() const override {
// 填充圆的逻辑
}
};
在这个例子中,IShape
和IColor
接口分别定义了不同的功能,避免了客户不必要的依赖。
2.5 依赖倒置原则(DIP)
高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
示例:
class Notification {
public:
virtual void send() = 0; // 抽象通知
};
class EmailNotification : public Notification {
public:
void send() override {
// 发送电子邮件逻辑
}
};
class SmsNotification : public Notification {
public:
void send() override {
// 发送短信逻辑
}
};
class User {
Notification* notifier;
public:
User(Notification* n) : notifier(n) {}
void notify() {
notifier->send(); // 使用抽象发送通知
}
};
在这个示例中,User
类依赖于Notification
接口而不是具体的通知实现,从而实现了依赖倒置原则。
3. 项目实践
在实际项目中,运用OOP的特性和设计原则是非常重要的。下面是一个基于OOP的简单项目示例。
3.1 项目背景
我们要创建一个简单的图形编辑器,能够支持绘制多种形状并计算其面积。通过OOP的特性,我们可以设计出灵活且易于扩展的结构。
3.2 类的设计
- Shape类作为抽象基类,定义绘制和计算面积的方法。
- 具体的形状类(如
Circle
、Rectangle
)继承自Shape
并实现相关方法。 - ShapeManager类负责管理所有形状,提供添加、删除和遍历功能。
3.3 代码示例
#include <iostream>
#include <vector>
#include <memory>
class Shape {
public:
virtual void draw() const = 0;
virtual double area() const = 0;
virtual ~Shape() = default; // 虚析构函数
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() const override {
std::cout << "Drawing Circle with radius: " << radius << std::endl;
}
double area() const override {
return 3.14 * radius * radius;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() const override {
std::cout << "Drawing Rectangle with width: " << width << " and height: " << height << std::endl;
}
double area() const override {
return width * height;
}
};
class ShapeManager {
private:
std::vector<std::shared_ptr<Shape>> shapes;
public:
void addShape(std::shared_ptr<Shape> shape) {
shapes.push_back(shape);
}
void drawAll() const {
for (const auto& shape : shapes) {
shape->draw();
}
}
void calculateTotalArea() const {
double totalArea = 0;
for (const auto& shape : shapes) {
totalArea += shape->area();
}
std::cout << "Total Area: " << totalArea << std::endl;
}
};
int main() {
ShapeManager manager;
manager.addShape(std::make_shared<Circle>(5.0));
manager.addShape(std::make_shared<Rectangle>(4.0, 6.0));
manager.drawAll();
manager.calculateTotalArea();
return 0;
}
在这个简单的图形编辑器项目中,我们通过OOP的特性和设计原则,实现了一个可扩展的框架。将来可以很方便地添加新的形状类,而无需修改现有代码。
总结
本课深入探讨了面向对象编程的基本特性,包括封装、继承、多态和抽象,以及如何应用这些特性设计出符合OOP原则的类。在实际开发中,遵循设计原则有助于编写可维护、可扩展的代码。通过简单的项目实例,我们展示了OOP在实际中的应用,帮助初级到中级程序员更好地理解和应用OOP的理念。
标签:const,示例,void,class,第一课,Shape,OOP,设计模式,public From: https://blog.csdn.net/u012263104/article/details/143103417