【HF设计模式】07-适配器模式 外观模式
声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。
摘要
《Head First设计模式》第7章笔记:结合示例应用和代码,介绍适配器模式和外观模式,包括遇到的问题、采用的解决方案、遵循的 OO 原则、以及达到的效果。
目录
- 摘要
- 1 适配器模式
- 1.1 适配器的概念
- 1.2 示例应用
- 1.3 适配器模式定义
- 1.4 示例代码
- 1.4.1 Java 示例
- 1.4.2 C++11 示例
- 2 外观模式
- 2.1 示例应用
- 2.2 遇到问题
- 2.3 引入设计模式
- 2.3.1 采用外观模式
- 2.3.2 外观模式定义
- 2.3.3 最少知识原则
- 2.4 示例代码
- 2.4.1 Java 示例
- 2.4.2 C++11 示例
- 3 设计工具箱
- 3.1 OO 基础
- 3.2 OO 原则
- 3.3 OO 模式
- 参考
还记得装饰者模式吗?“装饰者”将对象包装起来,赋予它新的职责。为了让“装饰者”可以充当“被装饰对象”,包装前后的对象具有相同的接口。
现在,我们同样需要将对象包装起来,不同的是,包装前后的对象具有不同的接口。之所以这样做,是为了将一个类适配到“具有不同接口要求”的系统中。
此外,我们还会趁热打铁,探讨另一种模式,它也将对象包装起来,为的是简化其接口。
1 适配器模式
1.1 适配器的概念
理解什么是 OO 适配器,也许并不难,因为在现实世界中,也存在着各种适配器。
例如电源适配器、蓝牙适配器、Type-C 转 HDMI 适配器等等。
同现实世界中的适配器一样,OO 适配器也是把一个接口转换成另一个接口,以解决接口不匹配的问题。
例如,现有软件系统需要整合一个新的类库,但是该类库提供的接口与系统所期望的接口并不匹配:
并且,我们既不能够改变类库的接口(没有源码或其它原因),也不能够改变系统的接口设计。
为此,我们可以实现一个适配器类,它将类库的接口适配成系统所期望的接口:
适配器接收来自系统的请求,并将其转换成类库能够理解的请求:
1.2 示例应用
具体的示例应用是一个“消息通知系统”。目前支持的通知类型包括邮件通知、应用内推送、微信通知。
所有类型的通知都实现 Notifier
接口:
现在,需要在系统中增加短信通知。短信服务由第三方提供,其接口(广义“接口概念”)如下:
短信服务 SMSSender
的接口与系统中的通知接口 Notifier
并不兼容。此时,我们可以使用适配器进行接口适配:
定义 SMSNotifierAdapter
适配器类:
- 实现
Notifier
接口,供系统调用:- 实现
init()
方法,初始化短信通知服务,具体功能通过SMSSender
对象完成; - 实现
notify()
方法,发送短信通知,具体功能通过SMSSender
对象完成; - 实现
formatMessage()
方法,将系统通用的消息格式转换为短信特定的格式(例如纯文本、字数限制等),
由于SMSSender
并未提供格式化功能,所以适配器需要自行实现(这也是适配器在接口转换过程中所必须承担的职责)。
- 实现
- 组合
SMSSender
对象,通过调用其接口,实现系统所需功能:- 声明
sender
成员变量,引用SMSSender
对象; - 在
init()
方法中,通过调用sender.connect()
与短信服务建立连接;
注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallConnectOfSender - 在
notify()
方法中,通过调用sender.sendMessage()
发送格式化后的短信消息。
- 声明
1.3 适配器模式定义
下面是适配器模式的正式定义:
适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
对于适配器 Adapter
类,将一个已经存在的接口 Adaptee
(被适配者),适配到客户 Client
所期望的接口 Target
(目标接口),可以采用两种方式:
方式1. 对象适配器,使用对象组合(组合“被适配者”)。
方式2. 类适配器,使用多重继承(继承“被适配者”)。(此处将“接口实现”也看作“继承”)
Target
,目标接口- 定义
Client
所使用的接口,它包含特定领域相关的操作,如request()
;
- 定义
Adaptee
,被适配者- 是一个已经存在的类,提供
specificRequest()
的实现,但是其接口与Target
不兼容;
- 是一个已经存在的类,提供
Adapter
,适配器- 实现
Target
接口; - 适配
Adaptee
接口:- 【对象适配器】组合
Adaptee
的对象,在request()
方法的实现中,通过委托调用adaptee.specificRequest()
; - 【类适配器】继承
Adaptee
的实现,在request()
方法的实现中,直接调用继承的specificRequest()
。
- 【对象适配器】组合
- 根据
Target
接口与Adaptee
接口的相似程度,Adapter
的工作范围会有所不同,从简单的接口转换(例如改变操作名)到支持完全不同的操作集合; Adapter
使Client
与Adaptee
解耦。
- 实现
Client
,适配器用户- 与符合
Target
接口的对象(如Adapter
对象)协作; - 依据
Target
接口,调用Adapter
对象的方法,并获得请求结果,在此过程中并不知道Adaptee
(甚至Adapter
) 的存在。
- 与符合
对象适配器与类适配器的区别,在本质上就是组合方式与继承方式的区别,主要对比如下:
特性 | 对象适配器(组合方式) | 类适配器(继承方式) |
---|---|---|
适配范围 | 可适配 Adaptee 类及其子类 | 仅适配特定的 Adaptee 类 |
功能扩展 | Adapter 类新增的功能,适用于所有被适配者 | Adapter 类新增的功能,仅适用于其继承的被适配者 |
行为修改 | 需要先创建 Adaptee 子类实现新行为,再适配该子类 | 子类 Adapter 可以直接覆盖父类 Adaptee 的方法 |
适配时机 | 运行时动态指定被适配者 | 编译时静态绑定适配关系 |
延伸阅读:《设计模式:可复用面向对象软件的基础》 Adapter(适配器)— 类对象结构型模式 [P106-115]
1.4 示例代码
注:代码仅为功能示意,未实现数据有效性验证、异常处理等。
1.4.1 Java 示例
通知接口 Notifier
定义,以及现有通知服务实现:
// Notifier.java
public interface Notifier {
public boolean init(String configFile);
public boolean notify(String receiver, String message);
public String formatMessage(String title, String content);
}
// EmailNotifier.java
public class EmailNotifier implements Notifier {
@Override
public boolean init(String configFile) {
System.out.println("Email notifier initialized with config file: " + configFile);
return true;
}
@Override
public boolean notify(String receiver, String message) {
System.out.println("Email sent to " + receiver + " with message: " + message);
return true;
}
@Override
public String formatMessage(String title, String content) {
String template = DEFAULT_TEMPLATE;
return String.format(template, title, content);
}
private static final String DEFAULT_TEMPLATE = "<!DOCTYPE html>" +
"<html><body>" +
"<h1>%s</h1><div>%s</div>" +
"</body></html>";
}
短信服务 SMSSender
的接口定义及其实现:
// SMSSender.java
public interface SMSSender {
public boolean connect(String account, String password);
public boolean sendMessage(String mobile, String message);
}
// SimpleSMSSender.java
public class SimpleSMSSender implements SMSSender {
@Override
public boolean connect(String account, String password) {
System.out.println("Connected to SMS server with account: " + account);
return true;
}
@Override
public boolean sendMessage(String mobile, String message) {
System.out.println("SMS sent to " + mobile + " with message: " + message);
return true;
}
}
短信通知适配器 SMSNotifierAdapter
:
// SMSNotifierAdapter.java
public class SMSNotifierAdapter implements Notifier {
private SMSSender smsSender;
public SMSNotifierAdapter(SMSSender smsSender) {
this.smsSender = smsSender;
}
@Override
public boolean init(String configFile) {
System.out.println("SMS notifier initializing with config file: " + configFile);
String account = "user001"; // 从配置文件中读取
String password = "pass001";
return smsSender.connect(account, password);
}
@Override
public boolean notify(String receiver, String message) {
System.out.println("SMS notifier sending message to " + receiver);
return smsSender.sendMessage(receiver, message);
}
@Override
public String formatMessage(String title, String content) {
String template = DEFAULT_TEMPLATE; // 根据配置文件设置模板
return String.format(template, title, content);
}
private static final String DEFAULT_TEMPLATE = "[%s] %s";
}
测试代码:
// NotificationSystem.java
public class NotificationSystem {
public static void main(String[] args) {
Notifier emailNotifier = new EmailNotifier();
Notifier smsNotifier = new SMSNotifierAdapter(new SimpleSMSSender());
Notifier[] notifiers = { emailNotifier, smsNotifier };
String[] receivers = { "abc@demo.com", "1234567890" };
System.out.println("\nInitializing notifiers...");
for (Notifier notifier : notifiers) {
notifier.init("config.json");
}
System.out.println("\nSending notifications...");
int messageCount = 2;
for (int i = 1; i <= messageCount; i++) {
String title = "Test";
String content = "Test message " + i;
for (int idx = 0; idx < notifiers.length; idx++) {
String message = notifiers[idx].formatMessage(title, content);
notifiers[idx].notify(receivers[idx], message);
}
}
}
}
1.4.2 C++11 示例
通知接口 Notifier
定义,以及现有通知服务实现:
struct Notifier {
virtual ~Notifier() = default;
virtual bool init(const std::string& configFile) = 0;
virtual bool notify(const std::string& receiver, const std::string& message) = 0;
virtual std::string formatMessage(const std::string& title, const std::string& content) = 0;
};
template <typename... Args>
std::string formatHelper(const char* fmt, Args... args) {
int size = std::snprintf(nullptr, 0, fmt, args...) + 1;
std::unique_ptr<char[]> buffer(new char[size]);
std::snprintf(buffer.get(), size, fmt, args...);
return std::string(buffer.get());
}
struct EmailNotifier : public Notifier {
bool init(const std::string& configFile) override {
std::cout << "Email notifier initialized with config file: " << configFile << '\n';
return true;
}
bool notify(const std::string& receiver, const std::string& message) override {
std::cout << "Email sent to " << receiver << " with message: " << message << '\n';
return true;
}
std::string formatMessage(const std::string& title, const std::string& content) override {
std::string msgTemplate = DEFAULT_TEMPLATE;
return formatHelper(msgTemplate.c_str(), title.c_str(), content.c_str());
}
private:
static constexpr const char* DEFAULT_TEMPLATE =
"<!DOCTYPE html>"
"<html><body>"
"<h1>%s</h1><div>%s</div>"
"</body></html>";
};
短信服务 SMSSender
的接口定义及其实现:
struct SMSSender {
virtual ~SMSSender() = default;
virtual bool connect(const std::string& account, const std::string& password) = 0;
virtual bool sendMessage(const std::string& mobile, const std::string& message) = 0;
};
struct SimpleSMSSender : public SMSSender {
bool connect(const std::string& account, const std::string& password) override {
std::cout << "Connected to SMS server with account: " << account << '\n';
return true;
}
bool sendMessage(const std::string& mobile, const std::string& message) override {
std::cout << "SMS sent to " << mobile << " with message: " << message << '\n';
return true;
}
};
短信通知适配器 SMSNotifierAdapter
:
struct SMSNotifierAdapter : public Notifier {
explicit SMSNotifierAdapter(std::shared_ptr<SMSSender> smsSender) : smsSender(smsSender) {}
bool init(const std::string& configFile) override {
std::cout << "SMS notifier initializing with config file: " << configFile << '\n';
std::string account = "user001"; // 从配置文件中读取
std::string password = "pass001";
return smsSender->connect(account, password);
}
bool notify(const std::string& receiver, const std::string& message) override {
std::cout << "SMS notifier sending message to " << receiver << '\n';
return smsSender->sendMessage(receiver, message);
}
std::string formatMessage(const std::string& title, const std::string& content) override {
std::string msgTemplate = DEFAULT_TEMPLATE; // 根据配置文件设置模板
return formatHelper(msgTemplate.c_str(), title.c_str(), content.c_str());
}
private:
std::shared_ptr<SMSSender> smsSender;
static constexpr const char* DEFAULT_TEMPLATE = "[%s] %s";
};
测试代码:
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// 在这里添加相关接口和类的定义
int main() {
auto emailNotifier = std::make_shared<EmailNotifier>();
auto smsNotifier = std::make_shared<SMSNotifierAdapter>(std::make_shared<SimpleSMSSender>());
std::vector<std::shared_ptr<Notifier>> notifiers = {emailNotifier, smsNotifier};
std::vector<std::string> receivers = {"abc@demo.com", "1234567890"};
std::cout << "\nInitializing notifiers...\n";
for (const auto& notifier : notifiers) {
notifier->init("config.json");
}
std::cout << "\nSending notifications...\n";
int messageCount = 2;
for (int i = 1; i <= messageCount; i++) {
std::string title = "Test";
std::string content = "Test message " + std::to_string(i);
for (size_t idx = 0; idx < notifiers.size(); idx++) {
std::string message = notifiers[idx]->formatMessage(title, content);
notifiers[idx]->notify(receivers[idx], message);
}
}
}
2 外观模式
无论是适配器模式,还是外观模式,都涉及接口转换:
- 对于适配器模式,转换接口,是为了匹配用户的接口要求;
- 对于外观模式,转换接口,是为了隐藏一个或多个类所具有的复杂性,为用户提供一个简单易用的接口。即,展现出一个整洁的外观(Facade)。
2.1 示例应用
外观的示例应用是一套家庭影院系统,其中包括流媒体播放器(streaming player)、投影仪(projector)、自动屏幕(screen)、环绕音响(amplifier),甚至还有爆米花机(popcorn popper)。
系统需要整合的组件如下:(有很多类、很多交互、以及一大群接口,需要学习和使用)
为了组装系统,包括布线、安装投影仪、连接所有设备并调试,花去了几个星期的时间。
现在,是时候让所有的设备运转起来,享受一部电影了……
2.2 遇到问题
噢,为了观赏电影,还需要执行一些操作:
- 打开爆米花机(对应:
popcornPopper.on()
) - 开始爆米花(对应:
popcornPopper.pop()
) - 放下屏幕(对应:
screen.down()
) - 打开投影仪(对应:
projector.on()
) - 设置投影仪为流媒体输入(对应:
projector.setInput(streamingPlayer)
) - 设置投影仪为宽屏模式(对应:
projector.wideScreenMode()
) - 打开功放(对应:
amplifier.on()
) - 设置功放为流媒体输入(对应:
amplifier.setStreamingPlayer(streamingPlayer)
) - 设置功放为环绕音响(对应:
amplifier.setSurroundSoud()
) - 设置功放为中音量(对应:
amplifier.setVolume(5)
) - 调暗灯光(对应:
theaterLights.dim(10)
) - 打开流媒体播放器(对应:
streamingPlayer.on()
) - 播放电影(对应:
streamingPlayer.play(movie)
)
累S!
而且,不仅如此,
- 电影结束后,怎样关掉设备?把上述步骤反向再来一遍?
- 收听音频是不是也要这样复杂?
系统需要升级!外观模式会把我们拉出泥潭……
2.3 引入设计模式
2.3.1 采用外观模式
只要实现外观类 HomeTheaterFacade
,就可以让系统变得易于使用:
外观类 HomeTheaterFacade
负责与流媒体播放器、投影仪、功放、屏幕等交互,并对外提供简化后的接口:
- 在开始观看电影时,只需要调用
watchMovie()
方法; - 在结束观看电影时,只需要调用
endMovie()
方法; - 同样,在开始和结束收听音频时,只需要调用
listenToRadio()
和endRadio()
方法。
如果需要访问系统原来的接口(例如调整音量 Amplifier::setVolume()
、暂停播放 StreamingPlayer::pause()
),它们也依然保留在那里,可以继续按照之前的方式进行访问。
2.3.2 外观模式定义
下面是外观模式的正式定义:
外观模式(Facade Pattern)
针对子系统中的一组接口,(为用户)提供一个统一的接口。外观定义了一个高层接口,这个接口使得这一子系统更加容易使用。
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
这里的“子系统”是指系统的一部分,它封装了一个或多个类,并对外提供接口。
例如,在下面外观模式的类图中,包含了一个 Component*
类的集合,并将其作为一个复杂子系统的示意。
Component*
,子系统中的一系列组件- 实现子系统的相关功能,并提供相应的接口。
Facade
,外观类- 提供一个简化的接口,封装与子系统之间的一系列复杂交互;
- 将
Client
的请求委派给子系统中适当的Component*
,从而实现相应功能; - 对
Client
隐藏子系统的复杂性,降低Client
与子系统的耦合度。
Client
,外观的用户- 通过
Facade
提供的接口,与子系统交互,而不必直接访问Component*
提供的接口。
- 通过
补充:
Facade
并不限制Client
访问子系统中Component*
提供的接口,因此Client
可以在易用性和灵活性之间进行选择;- 一个子系统可以具有多个外观,即可以定义
各种Facade
来分担职责。
延伸阅读:《设计模式:可复用面向对象软件的基础》 4.5 Facade(外观)— 对象结构型模式 [P139-146]
2.3.3 最少知识原则
采用外观模式,有助于我们遵循一条新的面向对象设计原则:
最少知识原则(Principle of Least Knowledge)
只与直接关联的对象交互。
Talk only to your immediate friends.
对于一个对象,它的“直接关联的对象”包括:
- 对象自身;
- 作为其成员变量的对象;
- 作为其方法参数传入的对象;
- 在其方法内部创建或实例化的对象。
“只与直接关联的对象交互”是指:对于任何对象,在它的任何方法中,只调用“直接关联的对象”的方法。这样做可以尽可能少地了解其他对象(即“最少知识”)。
最少知识原则引导我们:减少对象之间的交互,减少到仅发生在“直接关联的对象”之间。
遵循该原则,可以防止我们在设计中,将大量的类耦合在一起,导致“修改系统的一部分,会连锁影响到其他部分”。
在外观模式中,Client
只与 Facade
交互,这样,当子系统内部的 Component*
组件发生变化时,就不会影响到 Client
。
思考题:
下面的代码,是否违反最少知识原则?【答案在第 20 行】
public class House {
WeatherStation station;
// other methods and constructor
public float getTemp() {
return station.getThermometer().getTemperature();
}
}
答案:违反
解析:
通过 station.getThermometer() 获得的温度计对象,是“从其他方法返回的对象”,而不是“直接关联的对象”。
在这种情况下,可以为 WeatherStation 类增加一个方法 getTemperature(),由 station 直接返回温度值:
public float getTemp() {
return station.getTemperature();
}
另外,下面的实现怎么样?
public class House {
WeatherStation station;
// other methods and constructor
public float getTemp() {
Thermometer thermometer = station.getThermometer();
return getTempHelper(thermometer);
}
public float getTempHelper(Thermometer thermometer) {
return thermometer.getTemperature();
}
}
看起来确实没有违反最少知识原则,但是这和最初的实现比较,真的有不同吗?
最少知识原则告诉我们,不要调用“从其他方法返回的对象”的方法。
这听起来有些严厉,对吗?是否联想到 Java 中常用的 System.out.println()
?(TODO:这个例子不恰当)
遵循最少知识原则,虽然可以减少对象之间的依赖(降低耦合度),提高可维护性;但是,也会导致编写更多的 “包装方法/类”(wrapper),从而增加系统的复杂度,降低运行时性能。
所有的设计都有折中,所有的原则也都应该在权衡相关因素(灵活性与复杂度、可维护性与性能等)之后再使用。
最少知识原则的另一个名称是迪米特法则(Law of Demeter),因为没有原则是必须遵守的法律(law),所以我们更倾向于称之为最少知识原则。
2.4 示例代码
注:代码仅为功能示意,未实现数据有效性验证、异常处理等。
2.4.1 Java 示例
家庭影院组件类:
// StreamingPlayer.java
public class StreamingPlayer {
String description;
Amplifier amplifier;
String movie;
boolean surroundAudio;
public StreamingPlayer(String description, Amplifier amplifier) {
this.description = description;
this.amplifier = amplifier;
}
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void play(String movie) {
this.movie = movie;
System.out.println(description + " playing \"" + movie + "\"");
}
public void pause() { System.out.println(description + " paused \"" + movie + "\""); }
public void stop() { System.out.println(description + " stopped \"" + movie + "\""); }
public void setTwoChannelAudio() {
if (surroundAudio) {
surroundAudio = false;
System.out.println(description + " set two channel audio");
}
if (amplifier != null && amplifier.getSurroundSound()) amplifier.setStereoSound();
}
public void setSurroundAudio() {
if (!surroundAudio) {
surroundAudio = true;
System.out.println(description + " set surround audio");
}
if (amplifier != null && !amplifier.getSurroundSound()) amplifier.setSurroundSound();
}
public boolean getSurroundAudio() { return surroundAudio; }
public String toString() { return description; }
}
// Tuner.java
public class Tuner {
String description;
Amplifier amplifier;
double frequency;
public Tuner(String description, Amplifier amplifier) {
this.description = description;
this.amplifier = amplifier;
}
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void setAm() { System.out.println(description + " setting AM mode"); }
public void setFm() { System.out.println(description + " setting FM mode"); }
public void setFrequency(double frequency) {
System.out.println(description + " setting frequency to " + frequency);
this.frequency = frequency;
}
public String toString() { return description; }
}
// Amplifier.java
public class Amplifier {
String description;
Tuner tuner;
StreamingPlayer player;
boolean surroundSound;
public Amplifier(String description) { this.description = description; }
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void setVolume(int level) { System.out.println(description + " setting volume to " + level); }
public void setStereoSound() {
if (surroundSound) {
surroundSound = false;
System.out.println(description + " stereo mode on");
}
if (player != null && player.getSurroundAudio()) player.setTwoChannelAudio();
}
public void setSurroundSound() {
if (!surroundSound) {
surroundSound = true;
System.out.println(description + " surround sound on (5 speakers, 1 subwoofer)");
}
if (player != null && !player.getSurroundAudio()) player.setSurroundAudio();
}
public boolean getSurroundSound() { return surroundSound; }
public void setTuner(Tuner tuner) {
System.out.println(description + " setting tuner to " + tuner);
this.tuner = tuner;
}
public void setStreamingPlayer(StreamingPlayer player) {
System.out.println(description + " setting player to " + player);
this.player = player;
}
public String toString() { return description; }
}
// Projector.java
public class Projector {
String description;
StreamingPlayer player;
public Projector(String description) { this.description = description; }
public void setInput(StreamingPlayer player) {
System.out.println(description + " setting input to " + player);
this.player = player;
}
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void wideScreenMode() { System.out.println(description + " in widescreen mode (16x9 aspect ratio)"); }
public void tvMode() { System.out.println(description + " in tv mode (4x3 aspect ratio)"); }
public String toString() { return description; }
}
// Screen.java
public class Screen {
String description;
public Screen(String description) { this.description = description; }
public void up() { System.out.println(description + " going up"); }
public void down() { System.out.println(description + " going down"); }
public String toString() { return description; }
}
// TheaterLights.java
public class TheaterLights {
String description;
public TheaterLights(String description) { this.description = description; }
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void dim(int level) { System.out.println(description + " dimming to " + level + "%"); }
public String toString() { return description; }
}
// PopcornPopper.java
public class PopcornPopper {
String description;
public PopcornPopper(String description) { this.description = description; }
public void on() { System.out.println(description + " on"); }
public void off() { System.out.println(description + " off"); }
public void pop() { System.out.println(description + " popping popcorn!"); }
public String toString() { return description; }
}
家庭影院外观类:
// HomeTheaterFacade.java
public class HomeTheaterFacade {
Amplifier amp;
Tuner tuner;
StreamingPlayer player;
Projector projector;
Screen screen;
TheaterLights lights;
PopcornPopper popper;
public HomeTheaterFacade(Amplifier amp,
Tuner tuner,
StreamingPlayer player,
Projector projector,
Screen screen,
TheaterLights lights,
PopcornPopper popper) {
this.amp = amp;
this.tuner = tuner;
this.player = player;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}
public void watchMovie(String movie) {
System.out.println("\nGet ready to watch a movie...");
popper.on();
popper.pop();
screen.down();
projector.on();
projector.setInput(player);
projector.wideScreenMode();
amp.on();
amp.setStreamingPlayer(player);
amp.setSurroundSound();
amp.setVolume(5);
lights.dim(10);
player.on();
player.play(movie);
}
public void endMovie() {
System.out.println("\nShutting movie theater down...");
player.stop();
player.off();
lights.on();
amp.off();
projector.off();
screen.up();
popper.off();
}
public void listenToRadio(double frequency) {
System.out.println("\nTuning in the airwaves...");
tuner.on();
tuner.setFrequency(frequency);
amp.on();
amp.setVolume(5);
amp.setTuner(tuner);
}
public void endRadio() {
System.out.println("\nShutting down the tuner...");
amp.off();
tuner.off();
}
}
测试代码:
// HomeTheater.java
public class HomeTheater {
public static void main(String[] args) {
Amplifier amp = new Amplifier("Amplifier");
Tuner tuner = new Tuner("AM/FM Tuner", amp);
StreamingPlayer player = new StreamingPlayer("Streaming Player", amp);
Projector projector = new Projector("Projector");
Screen screen = new Screen("Theater Screen");
TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
PopcornPopper popper = new PopcornPopper("Popcorn Popper");
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, player, projector, screen, lights, popper);
homeTheater.watchMovie("Ne Zha 2");
homeTheater.endMovie();
}
}
2.4.2 C++11 示例
家庭影院组件类:
struct Amplifier;
struct StreamingPlayer {
StreamingPlayer(const std::string& description, std::weak_ptr<Amplifier> amplifier)
: description(description), amplifier(amplifier) {}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void play(const std::string& movie) {
this->movie = movie;
std::cout << description << " playing \"" << movie << "\"\n";
}
void pause() { std::cout << description << " paused \"" << movie << "\"\n"; }
void stop() { std::cout << description << " stopped \"" << movie << "\"\n"; }
void setTwoChannelAudio();
void setSurroundAudio();
bool isSurroundAudio() { return surroundAudio; }
private:
std::string description;
std::weak_ptr<Amplifier> amplifier;
std::string movie;
bool surroundAudio = false;
friend std::ostream& operator<<(std::ostream& os, const StreamingPlayer& player) { return os << player.description; }
};
struct Tuner {
Tuner(const std::string& description, std::weak_ptr<Amplifier> amplifier)
: description(description), amplifier(amplifier) {}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void setAm() { std::cout << description << " setting AM mode\n"; }
void setFm() { std::cout << description << " setting FM mode\n"; }
void setFrequency(double frequency) {
std::cout << description << " setting frequency to " << frequency << '\n';
this->frequency = frequency;
}
private:
std::string description;
double frequency;
std::weak_ptr<Amplifier> amplifier;
friend std::ostream& operator<<(std::ostream& os, const Tuner& tuner) { return os << tuner.description; }
};
struct Amplifier {
Amplifier(const std::string& description) : description(description) {}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void setVolume(int level) { std::cout << description << " setting volume to " << level << '\n'; }
void setStereoSound() {
if (surroundSound) {
surroundSound = false;
std::cout << description << " stereo mode on\n";
}
std::shared_ptr<StreamingPlayer> playerPtr = player.lock();
if (playerPtr && playerPtr->isSurroundAudio()) playerPtr->setTwoChannelAudio();
}
void setSurroundSound() {
if (!surroundSound) {
surroundSound = true;
std::cout << description << " surround sound on (5 speakers, 1 subwoofer)\n";
}
std::shared_ptr<StreamingPlayer> playerPtr = player.lock();
if (playerPtr && !playerPtr->isSurroundAudio()) playerPtr->setSurroundAudio();
}
bool isSurroundSound() { return surroundSound; }
void setTuner(std::weak_ptr<Tuner> tuner) {
std::shared_ptr<Tuner> tunerPtr = tuner.lock();
if (tunerPtr) std::cout << description << " setting tuner to " << *tunerPtr << '\n';
this->tuner = tuner;
}
void setStreamingPlayer(std::weak_ptr<StreamingPlayer> player) {
std::shared_ptr<StreamingPlayer> playerPtr = player.lock();
if (playerPtr) std::cout << description << " setting player to " << *playerPtr << '\n';
this->player = player;
}
private:
std::string description;
std::weak_ptr<Tuner> tuner;
std::weak_ptr<StreamingPlayer> player;
bool surroundSound = false;
friend std::ostream& operator<<(std::ostream& os, const Amplifier& amp) { return os << amp.description; }
};
void StreamingPlayer::setTwoChannelAudio() {
if (surroundAudio) {
surroundAudio = false;
std::cout << description << " set two channel audio\n";
}
std::shared_ptr<Amplifier> amplifierPtr = amplifier.lock();
if (amplifierPtr && amplifierPtr->isSurroundSound()) amplifierPtr->setStereoSound();
}
void StreamingPlayer::setSurroundAudio() {
if (!surroundAudio) {
surroundAudio = true;
std::cout << description << " set surround audio\n";
}
std::shared_ptr<Amplifier> amplifierPtr = amplifier.lock();
if (amplifierPtr && !amplifierPtr->isSurroundSound()) amplifierPtr->setSurroundSound();
}
struct Projector {
Projector(const std::string& description) : description(description) {}
void setInput(std::weak_ptr<StreamingPlayer> player) {
std::shared_ptr<StreamingPlayer> playerPtr = player.lock();
if (playerPtr) std::cout << description << " setting input to " << *playerPtr << '\n';
this->player = player;
}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void wideScreenMode() { std::cout << description << " in widescreen mode (16x9 aspect ratio)\n"; }
void tvMode() { std::cout << description << " in tv mode (4x3 aspect ratio)\n"; }
private:
std::string description;
std::weak_ptr<StreamingPlayer> player;
friend std::ostream& operator<<(std::ostream& os, const Projector& projector) { return os << projector.description; }
};
struct Screen {
Screen(const std::string& description) : description(description) {}
void up() { std::cout << description << " going up\n"; }
void down() { std::cout << description << " going down\n"; }
private:
std::string description;
friend std::ostream& operator<<(std::ostream& os, const Screen& screen) { return os << screen.description; }
};
struct TheaterLights {
TheaterLights(const std::string& description) : description(description) {}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void dim(int level) { std::cout << description << " dimming to " << level << "%\n"; }
private:
std::string description;
friend std::ostream& operator<<(std::ostream& os, const TheaterLights& lights) { return os << lights.description; }
};
struct PopcornPopper {
PopcornPopper(const std::string& description) : description(description) {}
void on() { std::cout << description << " on\n"; }
void off() { std::cout << description << " off\n"; }
void pop() { std::cout << description << " popping popcorn!\n"; }
private:
std::string description;
friend std::ostream& operator<<(std::ostream& os, const PopcornPopper& popper) { return os << popper.description; }
};
家庭影院外观类:
struct HomeTheaterFacade {
HomeTheaterFacade(std::shared_ptr<Amplifier> amp, std::shared_ptr<Tuner> tuner,
std::shared_ptr<StreamingPlayer> player, std::shared_ptr<Projector> projector,
std::shared_ptr<Screen> screen, std::shared_ptr<TheaterLights> lights,
std::shared_ptr<PopcornPopper> popper)
: amp(amp), tuner(tuner), player(player), projector(projector), screen(screen), lights(lights), popper(popper) {}
void watchMovie(const std::string& movie) {
std::cout << "\nGet ready to watch a movie...\n";
popper->on();
popper->pop();
screen->down();
projector->on();
projector->setInput(player);
projector->wideScreenMode();
amp->on();
amp->setStreamingPlayer(player);
amp->setSurroundSound();
amp->setVolume(5);
lights->dim(10);
player->on();
player->play(movie);
}
void endMovie() {
std::cout << "\nShutting movie theater down...\n";
player->stop();
player->off();
lights->on();
amp->off();
projector->off();
screen->up();
popper->off();
}
void listenToRadio(double frequency) {
std::cout << "\nTuning in the airwaves...\n";
tuner->on();
tuner->setFrequency(frequency);
amp->on();
amp->setVolume(5);
amp->setTuner(tuner);
}
void endRadio() {
std::cout << "\nShutting down the tuner...\n";
amp->off();
tuner->off();
}
private:
std::shared_ptr<Amplifier> amp;
std::shared_ptr<Tuner> tuner;
std::shared_ptr<StreamingPlayer> player;
std::shared_ptr<Projector> projector;
std::shared_ptr<Screen> screen;
std::shared_ptr<TheaterLights> lights;
std::shared_ptr<PopcornPopper> popper;
};
测试代码:
#include <iostream>
#include <memory>
#include <string>
// 在这里添加相关接口和类的定义
int main() {
auto amp = std::make_shared<Amplifier>("Amplifier");
auto tuner = std::make_shared<Tuner>("AM/FM Tuner", amp);
auto player = std::make_shared<StreamingPlayer>("Streaming Player", amp);
auto projector = std::make_shared<Projector>("Projector");
auto screen = std::make_shared<Screen>("Theater Screen");
auto lights = std::make_shared<TheaterLights>("Theater Ceiling Lights");
auto popper = std::make_shared<PopcornPopper>("Popcorn Popper");
auto homeTheater = std::make_shared<HomeTheaterFacade>(amp, tuner, player, projector, screen, lights, popper);
homeTheater->watchMovie("Ne Zha 2");
homeTheater->endMovie();
}
3 设计工具箱
3.1 OO 基础
OO 基础回顾
- 抽象(Abstraction)
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
3.2 OO 原则
3.2.1 新原则
只与直接关联的对象交互。
Talk only to your immediate friends.
3.2.2 原则回顾
- 封装变化。
Encapsulate what varies. - 针对接口编程,而不是针对实现编程。
Program to interfaces, not implementations. - 优先使用组合,而不是继承。
Favor composition over inheritance. - 尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact. - 类应该对扩展开放,对修改关闭。
Classes should be open for extension, but closed for modification. - 依赖抽象,不依赖具体类。
Depend on abstractions. Do not depend on concrete classes.
3.3 OO 模式
3.3.1 新模式
- 适配器模式(Adapter Pattern)
- 将一个类的接口转换成客户期望的另一个接口。
The Adapter Pattern converts the interface of a class into another interface clients expect. - 适配器让原本接口不兼容的类可以合作。
Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
- 将一个类的接口转换成客户期望的另一个接口。
- 外观模式(Facade Pattern)
- 提供一个统一的接口,用来访问子系统中的一组接口。
The Facade Pattern provides a unified interface to a set of interfaces in a subsystem. - 外观定义了一个高层接口,使得子系统更易于使用。
Facade defines a higher-level interface that makes the subsystem easier to use.
- 提供一个统一的接口,用来访问子系统中的一组接口。
3.3.2 模式回顾
1 创建型模式(Creational Patterns)
创建型模式与对象的创建有关。
Creational patterns concern the process of object creation.
- 工厂方法(Factory Method)
- 定义了一个创建对象的接口,但由子类决定要实例化哪个类。
The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. - 工厂方法让类把实例化推迟到子类。
Factory Method lets a class defer instantiation to subclasses.
- 定义了一个创建对象的接口,但由子类决定要实例化哪个类。
- 抽象工厂(Abstract Factory)
- 提供一个接口,创建相关或依赖对象的家族,而不需要指定具体类。
The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- 提供一个接口,创建相关或依赖对象的家族,而不需要指定具体类。
- 单例模式(Singleton Pattern)
- 确保一个类只有一个实例,并提供一个全局访问点。
The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it.
- 确保一个类只有一个实例,并提供一个全局访问点。
2 结构型模式(Structural Patterns)
结构型模式处理类或对象的组合。
Structural patterns deal with the composition of classes or objects.
- 装饰者模式(Decorator Pattern)
- 动态地给一个对象添加一些额外的职责。
The Decorator Pattern attaches additional responsibilities to an object dynamically. - 就增加功能来说,装饰者模式相比生成子类更为灵活。
Decorators provide a flexible alternative to subclassing for extending functionality.
- 动态地给一个对象添加一些额外的职责。
3 行为型模式(Behavioral Patterns)
行为型模式描述类或对象之间的交互方式以及职责分配方式。
Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.
- 策略模式(Strategy Pattern)
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. - 让算法的变化独立于使用算法的客户。
Strategy lets the algorithm vary independently from clients that use it.
- 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
- 观察者模式(Observer Pattern)
- 定义对象之间的一对多依赖,
The Observer Pattern defines a one-to-many dependency between objects - 这样一来,当一个对象改变状态时,它的所有依赖者都会被通知并自动更新。
so that when one object changes state, all of its dependents are notified and updated automatically.
- 定义对象之间的一对多依赖,
- 命令模式(Command Pattern)
- 把请求封装为对象,
The Command Pattern encapsulates a request as an object, - 以便用不同的请求来参数化客户,对请求进行排队或记录请求日志,并支持可撤销的操作。
thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
- 把请求封装为对象,
参考
- [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
- [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
- wickedlysmart: Head First设计模式 Java 源码
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.