跳到主要内容

13 篇博文 含有标签「设计模式」

设计模式

查看所有标签

发布订阅模式 (PubSub)

· 阅读需 2 分钟

与观察者模式类似,但发布者和观察者不直接沟通,而是通过一个发布订阅中心,通过 event 消息来间接沟通。

发布订阅中心接收来自发布者的消息,然后通知订阅了该消息的订阅者们。

https://www.toptal.com/ruby-on-rails/the-publish-subscribe-pattern-on-rails

比喻一下,有个报亭,小A来注册了“来报”事件,她希望来报时打电话告诉他来报了。小B也到报亭注册了“来报”事件,他希望来报时发短信告诉他来报了。订阅、退订和通知,这三件事都是发布订阅中心来做。

根据上述比喻,写的示例代码:

interface IPubSub {
subscribe(event: string, fn: any): void;
unSubscribe(event: string, fn: any): void;
publish(event: string, data: any): void;
}

// 订阅发布中心
class PubSub implements IPubSub {
private subs: any = {};

// 注册事件和对应处理函数
subscribe(event: string, fn: any): void {
if (this.subs[event]) {
this.subs[event].push(fn);
} else {
this.subs[event] = [fn];
}
}

unSubscribe(event: string, fn: any): void {
this.subs[event] = this.subs[event].filter((sub: any) => {
sub !== fn;
});
}

// 发布者发布消息
publish(event: string, data: any): void {
for (const fn of this.subs[event]) {
fn(data);
}
}
}

const pubcenter = new PubSub();

// 注册 data 事件,事件触发时,打电话告诉 A 报纸到了
pubcenter.subscribe('data', (data: string) => {
console.log('Calling Person A: Your newspaper arrives.');
});
// 也为 B 注册 data 事件,事件触发时,发短信告诉它报纸到了
pubcenter.subscribe('data', (data: string) => {
console.log('Texting Person B: Your newspaper arrives.');
});

pubcenter.publish('data', 'Daaaaaaaaaata.');

输出:

Calling Person A: Your newspaper arrives.
Texting Person B: Your newspaper arrives.

观察者模式 (Observer)

· 阅读需 2 分钟

观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。

  • 开闭原则,你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。
  • 你可以在运行时建立对象之间的联系。
  • 订阅者的通知顺序是随机的。

示例

具体观察者 ConcreteObserverA 实现 Observer 接口,ConcreteSubject 实现 Subject 接口。

interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;

// 通知所有订阅者
notify(): void;
}

interface Observer {
update(): void;
}

class ConcreteSubject implements Subject {
// 观察者列表
private observerList: Observer[] = [];

attach(observer: Observer): void {
this.observerList.push(observer);
}
detach(observer: Observer): void {
this.observerList.splice(this.observerList.indexOf(observer), 1);
}

// 遍历通知每个观察者
notify(): void {
for (const observer of this.observerList) {
observer.update();
}
}
}

class ConcreteObserverA implements Observer {
update(): void {
console.log('ObserverA update');
}
}

class ConcreteObserverB implements Observer {
update(): void {
console.log('ObserverB update');
}
}

const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();
subject.attach(observerA);
subject.attach(observerB);

subject.notify();

输出:

ObserverA update
ObserverB update

命令模式 (Command)

· 阅读需 4 分钟

命令模式是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。

  • 单一职责原则。可以解耦触发执行操作的类。
  • 开闭原则。可以在不修改已有客户端代码的情况下在程序中创建新的命令。
  • 可以实现撤销和恢复功能。
  • 可以实现操作的延迟执行。
  • 可以将一组简单命令组合成一个复杂命令。
  • 代码可能会变得更加复杂,因为在发送者和接收者之间增加了一个全新的层次。

示例

下面的示例模拟了一个应用,点击按钮,复制文本到剪贴板的场景:

Command 是抽象命令类,基于该类派生出 CopyCommand 具体类,用于执行复制操作。Application 是发送者类,将复制命令发送给 Button 接收者类,由他执行命令。

// 抽象命令类
abstract class Command {
protected editor: Editor;
protected app: Application;

constructor(editor: Editor, app: Application) {
this.editor = editor;
this.app = app;
}

// 抽象执行方法
abstract execute(): void;
}

// 具体复制命令类,继承了抽象命令类,一定要调用 super 构造器
class CopyCommand extends Command {
constructor(editor: Editor, app: Application) {
super(editor, app);
}

// 实现执行复制的方法
public execute(): void {
const text = this.editor.getSelection();
this.app.setClipboard(text);
}
}

// 编辑器类,可以添加、选取、删除文本
class Editor {
private text: string = '';

public getSelection(start?: number, stop?: number) {
return this.text.slice(start, stop);
}

public addText(text: string) {
this.text += text;
}

public deleteText(start?: number, stop?: number) {
this.text.replace(this.text.slice(start, stop), '');
}
}

// 按钮类(接收者),可以设置、执行命令
class Button {
private command?: Command;

public setCommand(command: Command) {
this.command = command;
}

public onClick() {
this.command?.execute();
}
}

// 应用类(发送者),将命令委派给接收者 Button 执行
class Application {
private clipboard: string;
private button: Button;
private editor: Editor;

// 初始化剪贴板、编辑区、按钮
constructor() {
this.clipboard = '';
this.button = new Button();
this.editor = new Editor();
this.button.setCommand(new CopyCommand(this.editor, this));
}

// 向编辑器中写入文本
public WriteSomethingInEditor(text: string) {
this.editor.addText(text);
}

// 设置剪贴板内容
public setClipboard(text: string) {
this.clipboard = text;
}

// 获取剪贴板内容
public getClipboard() {
return this.clipboard;
}

// 复制按钮点击事件,将设置编辑器中所选文本到剪贴板
public clickCopy() {
this.button.onClick();
}
}

const app = new Application();
// 向编辑框写入文本
app.WriteSomethingInEditor('Something.');
// 点击复制按钮
app.clickCopy();
// 获取剪贴板内容
console.log(app.getClipboard()); // Something.

迭代器模式 (Iterator)

· 阅读需 3 分钟

迭代器模式是一种行为设计模式,可以在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。

  • 单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类,可对客户端代码和集合进行整理。
  • 开闭原则。可实现新型的集合和迭代器并将其传递给现有代码,无需修改现有代码。
  • 可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态。
  • 可以暂停遍历并在需要时继续。
  • 如果你的程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
  • 对于某些集合,使用迭代器可能比直接遍历的效率低。

示例

举例说明,以下代码对微信好友进行遍历。定义了两个接口 SocialNetwork、ProfileIterator{''} 和两个类 Wechat、WechatIterator。

Wechat 类要实现 SocialNetwork 接口,来实现创建微信迭代器的工厂方法,并储存好友列表、提供增加好友和获取好友的方法。

WeChatIterator 类要实现 ProfileIterator{''} 接口,来实现一些具体的微信迭代器方法,用来迭代好友列表。

// 声明用于生成迭代器的工厂方法
interface SocialNetwork {
createFriendsIterator(): ProfileIterator<string | null>;
}

// 声明迭代器接口
interface ProfileIterator<T> {
// 返回当前项,并向后移一位
next(): T;
// 返回当前项
current(): T;
}

// 实现生成迭代器的工厂方法,和一些 Wechat 类自有的方法
class Wechat implements SocialNetwork {
private friendList: string[] = [];

createFriendsIterator(): ProfileIterator<string | null> {
// 迭代器指向本实例
return new WeChatIterator(this);
}

public addFriend(friend: string) {
this.friendList.push(friend);
}

public getFriendList() {
return this.friendList;
}
}

// 实现具体的微信好友迭代器类
class WeChatIterator implements ProfileIterator<string | null> {
private wechat: Wechat; // 微信实例
private position = 0; // 记录当前迭代到的位置

constructor(weChat: Wechat) {
this.wechat = weChat;
}

// 返回当前项,并后移一位;若已经遍历完,没有数据了,返回 null
next(): string | null {
return this.wechat.getFriendList()[this.position++] || null;
}

current(): string {
return this.wechat.getFriendList()[this.position];
}
}

const wechat = new Wechat();
wechat.addFriend('Lee');
wechat.addFriend('Tom');
wechat.addFriend('Bella');

const iterator = wechat.createFriendsIterator();

let curr;
while ((curr = iterator.next())) {
console.log(curr);
}

输出:

Lee
Tom
Bella

外观模式 (Facade)

· 阅读需 3 分钟

外观模式,是结构型设计模式,为程序库、框架或者其他复杂类提供一个简单的接口。

  • 你可以让自己的代码独立于复杂子系统。
  • 外观可能成为与程序中所有类都耦合的上帝对象。

在现实生活中,就像电话订餐服务,你只需要给餐厅打电话,服务员会根据你的要求,进行下单、分派到后厨、记账、打包、派送,一系列复杂的操作是对客户隐藏的,客户不需要知道这么多,只需要告诉她你要吃什么。

示例

播放一个视频文件十分复杂,用到很多类,对文件解码、提取音频、计算比特率等等,在每个需要播放视频的地方,如果每个地方都用这么多代码,实在是太复杂了,代码也显得难看。比较好的办法,就是把播放功能的这些逻辑代码都写在一个外观类里,到的时候只需要用外观类就可以了。

class VideoFile {
constructor(path: string) {}
// ...
}
class BitrateCalc {
// ...
}
class AudioExtract {
constructor(path: VideoFile) {}
// ...
}
class MPEGDecoder {
constructor(path: VideoFile) {}
// ...
}

// 为了将框架的复杂性隐藏在一个简单接口背后,我们创建了一个外观类,它是在
// 功能性和简洁性之间做出的权衡
class VideoPlayer {
play(filePath: string) {
const file = new VideoFile(filePath);
console.log(new BitrateCalc());
new AudioExtract(file);
new MPEGDecoder(file);
// ...
// 一堆复杂的处理,播放视频文件
}
}

function ClientCode(filePath: string) {
// 只要调用外观类封装好的方法
new VideoPlayer().play(filePath);
}