C++ 中的接口设计
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 61 课,共 104 课。
C++ 中的接口是一种抽象类,其中每个成员函数(虚析构函数除外)都是纯虚函数。与可能包含部分实现的抽象类不同,接口只定义必须存在的操作,它们规定了一种契约,但不包含任何行为。
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};这个 Drawable 接口可以确保任何实现它的类都具有 draw() 和 resize() 方法,但并未说明它们如何工作。这种分离很有用:代码可以依赖该接口,而无需了解具体类型的任何信息。
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* 调整按钮大小 */ }
};一个类可以实现多个接口,从而支持灵活的设计,让对象能够扮演不同的角色:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};接口有助于实现松耦合:你的代码依赖抽象,而不是具体实现,因此更容易扩展和测试。
挑战
简单我们来构建一个设备管理系统,演示接口如何为不同类型的设备定义契约。你将创建两个设备可以实现的独立接口,然后构建满足其中一个或两个契约的具体设备类。
你将把代码分成 3 个文件:
Interfaces.h:定义两个表示不同设备能力的纯抽象类(接口):Powerable:任何可以开关机的设备:- 一个纯虚方法
powerOn() - 一个纯虚方法
powerOff() - 一个纯虚方法
getPowerStatus(),返回std::string - 一个虚析构函数
Connectable:任何可以连接到网络的设备:- 一个纯虚方法
connect(const std::string& network) - 一个纯虚方法
disconnect() - 一个纯虚方法
getConnectionInfo(),返回std::string - 一个虚析构函数
- 一个纯虚方法
Devices.h:实现使用这些接口的具体设备类:Lamp:只实现Powerable:- 一个私有成员
bool isOn(初始值为false) - 一个私有成员
std::string name - 一个接收灯具名称的构造函数
powerOn()将isOn设为 true,并打印:<name>: Light turned onpowerOff()将isOn设为 false,并打印:<name>: Light turned offgetPowerStatus()根据状态返回"ON"或"OFF"
SmartTV:同时实现Powerable和Connectable:- 私有成员:
bool isOn(初始值为 false)、std::string brand、std::string currentNetwork(初始值为空) - 一个接收电视品牌的构造函数
powerOn()将isOn设为 true,并打印:<brand> TV: Powered onpowerOff()将isOn设为 false,清空网络,并打印:<brand> TV: Powered offgetPowerStatus()返回"ON"或"OFF"connect()保存网络名称,并打印:<brand> TV: Connected to <network>disconnect()清空网络,并打印:<brand> TV: Disconnected- 如果已连接,
getConnectionInfo()返回"Connected to <network>";如果网络为空,则返回"Not connected"
- 一个私有成员
main.cpp:读取 3 个输入(每个输入各占一行):- 灯具名称
- 电视品牌
- 网络名称
创建一个
Lamp和一个SmartTV。演示如何对不同设备使用同一个接口:首先,通过
Powerable接口操作这两个设备。将它们的指针存入一个Powerable*数组,然后循环遍历数组,对每个设备调用powerOn(),接着按以下格式打印它们的状态:Status: <powerStatus>打印一个空行,然后通过
Connectable接口操作SmartTV。创建一个指向该SmartTV的Connectable*指针,使用网络名称调用connect(),并打印:Connection: <connectionInfo>再打印一个空行,然后通过
Powerable数组关闭这两个设备,并显示它们的最终状态。
例如,输入 Desk Lamp、Samsung 和 HomeWiFi 时:
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFF请注意,根据你使用的接口指针,SmartTV 可以被视为 Powerable 或 Connectable。这正是实现多个接口所带来的灵活性。你的代码可以与任何满足所需契约的设备协作,而不必知道它的具体类型。
亲手试试
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// 读取输入
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO:创建一个 Lamp 对象和一个 SmartTV 对象
// TODO:创建一个包含这两个设备的 Powerable* 指针数组
// TODO:遍历数组,对每个设备调用 powerOn()
// 每次调用 powerOn() 后,打印:Status: <powerStatus>
// TODO:打印一个空行
// TODO:创建一个指向 SmartTV 的 Connectable* 指针
// 用网络名称调用 connect()
// 打印:Connection: <connectionInfo>
// TODO:打印一个空行
// TODO:遍历 Powerable 数组,对每个设备调用 powerOff()
// 每次调用 powerOff() 后,打印:Status: <powerStatus>
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器