每日设计模式

2025-05-24

每日设计模式

照片由Flo P 在 Unsplash 上拍摄

最初发布在我的网站上

大家好!几周前,我开始了一项新挑战,每天学习一种设计模式,我称之为
#DesignPatternsEveryday ”。

既然我完成了挑战,我想我应该简要分享一下我所学到的设计模式,让我们
开始吧。

我将介绍大多数模式并用我自己的话来解释它们,所以如果您发现任何错误或
错误信息,请告诉我。我不是设计模式专家。

目录

什么是设计模式?

设计模式是软件设计中常见问题的典型解决方案。每个模式都像是一个蓝图,你
可以自定义它来解决代码中的特定设计问题。—— refactoring.guru

设计模式有 3 类,我们将逐一介绍它们。

  • 创建型
    提供了一种创建新对象的方法,从而增加了灵活性和可重用性。

  • 结构
    有助于构建和组装对象和类,同时使其变得灵活。

  • 行为
    有助于对象之间的沟通并关注对象之间的责任。

还要注意,在项目中运用设计模式时,有一件事非常重要。

永远不要一开始就抱着“好吧,我要在代码库中使用{这个模式}”的心态。

判断和分析代码库,首先规划逻辑和实现,然后
仅在必要时应用设计模式来解决任何特定问题。

设计模式是解决问题的方法,而不是寻找问题的解决方案。

第 1 天

  • 抽象工厂模式

抽象工厂是一种创建型设计模式,它允许我们生成对象系列而无需指定它们的
具体类。

假设您正在创建一个绘图应用程序,其中您将拥有“绘制框”、“绘制圆圈”等工具,但您还需要
框和圆圈的圆形变体,在这种情况下,您可以为“ShapeFactory”和“RoundedShapeFactory”创建一个工厂,它们将
返回相应的形状。

用例

当你需要一个跨平台的框架时,抽象工厂模式可能会很有帮助,例如
“Electronjs”。我不知道Electronjs如何处理这个问题,可能不能用工厂模式,但可以
用工厂模式实现。

  • 例子

github 上的代码

class Button {
  render() {}
}

class Factory {
  createButton() {}
}

class WinButton extends Button {
  render() {
    return "<button class='windows'></button>";
  }
}

class LinuxButton extends Button {
  render() {
    return "<button class='linux'></button>";
  }
}

class WinFactory extends Factory {
  createButton() {
    return new WinButton();
  }
}
class LinuxFactory extends Factory {
  createButton() {
    return new LinuxButton();
  }
}

class AbstractFactory {
  static factory(type) {
    switch (type) {
      case 'windows':
        return new WinFactory();
      case 'linux':
        return new LinuxFactory();

      default:
        break;
    }
  }
}

let guiFactory = AbstractFactory.factory('linux');
let button = guiFactory.createButton();
console.log(button.render());
Enter fullscreen mode Exit fullscreen mode

第 2 天

  • 建造者模式

建造者模式是一种创建型设计模式,它允许我们逐步创建复杂的对象。它
允许我们用相同的代码创建不同类型的对象。

现实世界类比

可以把它想象成汽车装配线。汽车会一步步地用零件组装起来,首先是底盘,
然后是发动机、散热器、车轮、座椅、车门。通过修改装配线上的这些步骤,我们可以
用同一条装配线打造不同类型的车型。

用例

当你想要创建具有不同表示形式的各种对象,而无需
为每个对象创建子类时,建造者模式非常有用。

我在之前的项目
Evolution Aquarium中实现了建造者模式,用于构建具有不同行为和特征的不同类型的 Boid

  • 例子

github 上的代码

class Car {
  constructor(engine, fuelTank, seats) {
    this.engine = engine;
    this.fuelTank = fuelTank;
    this.seats = seats;
  }

  printSpecs() {
    console.log(this.engine, this.fuelTank, this.seats);
  }
}
class CarBuilder {
  constructor() {
    this.engine = '';
    this.seats = '';
    this.fuelTank = '';
  }

  addSeats(name) {
    this.seats = name;
    return this;
  }
  addEngine(value) {
    this.engine = value;
    return this;
  }
  addFuelTank(value) {
    this.fuelTank = value;
    return this;
  }

  build() {
    return new Car(this.engine, this.fuelTank, this.seats);
  }
}

let truck = new CarBuilder()
  .addSeats(8)
  .addEngine('v12')
  .addFuelTank('200liters')
  .build();

let sedan = new CarBuilder()
  .addSeats(4)
  .addEngine('v6')
  .addFuelTank('100liters')
  .build();
Enter fullscreen mode Exit fullscreen mode

第 3 天

  • 工厂方法

工厂方法模式,它与抽象工厂方法类似,但也有一些细微的差别。抽象工厂
模式会根据类型创建工厂及其子工厂。(我觉得这么说有点冗长)但工厂
方法模式非常简单,它只有一个工厂。

用例

DOM API 的方法是一种工厂方法。它根据 传递的类型document.createElement创建不同类型的 HTML 元素。

  • 例子

github 上的代码

class Document {
  render() {
    return null;
  }
}
class Div extends Document {
  render() {
    return '<div />';
  }
}
class Section extends Document {
  render() {
    return '<section />';
  }
}
class DOMFactory {
  createElement(type) {
    switch (type) {
      case 'div':
        return new Div();
      case 'section':
        return new Section();
      default:
        break;
    }
  }
}

let domFactory = new DOMFactory();
let div = domFactory.createElement('div');
let section = domFactory.createElement('section');
Enter fullscreen mode Exit fullscreen mode

第 4 天

  • 单例模式

单例设计模式是一种创建型设计模式,可确保一个类只有一个实例。

现实世界类比

现实世界中单身人士的一个很好的类比是政府,一个国家只能有一个政府,无论
它由多少人组成,它的头衔总是“{Country} 政府”

  • 例子

github 上的代码

class Singleton {
  static instance = new Singleton();
  static getInstance() {
    return this.instance;
  }

  showMessage() {
    console.log('Hello singleton');
  }
}

let instance1 = Singleton.getInstance();
let instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

instance2.showMessage();
Enter fullscreen mode Exit fullscreen mode

开始结构设计模式

第 5 天

  • 适配器模式

适配器模式是一种结构设计模式,它充当两个不同接口/API 之间的转换器。

用例

如果您有两个不同的 API 并且想要一个通用接口来处理
它们,那么这种模式可能会很有用。

让我们举个例子。假设您正在为 Web 构建一个支持 WebGL 和 CanvasAPI 的 2D 渲染器,您可以制作
一个通用渲染 API 并使用适配器模式来填补它们之间的空白。

  • 例子

代码在 github 上

class Python {
  print(msg: string) {
    return console.log(msg);
  }
}
class Javascript {
  console(msg: string) {
    return console.log(msg);
  }
}

class LoggerAdapter {
  adapter: any;
  constructor(type: string) {
    if (type === 'py') {
      this.adapter = new Python();
    } else if (type === 'js') {
      this.adapter = new Javascript();
    }
  }

  log(type: string, msg: string) {
    if (type === 'py') {
      this.adapter.print(msg);
    } else if (type === 'js') {
      this.adapter.console(msg);
    }
  }
}

class Logger {
  adapter: any;
  log(type: string, msg: string) {
    this.adapter = new LoggerAdapter(type);
    this.adapter.log(type, msg);
  }
}

const logger = new Logger();

logger.log('js', 'Hello world js');
logger.log('py', 'Hello world py');
Enter fullscreen mode Exit fullscreen mode

第 6 天

  • 桥梁图案

“将抽象与其实现分离,以便两者可以独立变化”什么?

嗯,这确实令人困惑,但看看这种模式有多么有用还是很有趣的。

基本上,桥接模式允许我们将平台相关逻辑与平台无关逻辑分开。

这对于构建用户界面很有用,在用户界面中,您希望根据不同的
资源创建不同的视图,而以传统方式执行此操作将迫使您分别实现每个视图及其资源
实现,并且将成倍增加复杂耦合类的数量。

但通过桥接,我们可以通过使用统一资源接口与抽象视图类进行通信来解决这个问题。

  • 例子

github 上的代码

interface IResource {
  title: "() => string;"
  body: () => string;
  link: () => string;
  image: () => string;
}

abstract class View {
  resource: IResource;
  constructor(resource: IResource) {
    this.resource = resource;
  }

  render(): string {
    return '';
  }
}

class DetailedView extends View {
  render() {
    return `
    <div>
      <h2>${this.resource.title()}</h2>
      <img src="${this.resource.image()}" />  
      <div>${this.resource.body()}</div>
      <a href="${this.resource.link()}">readmore</a>
    </div>
    `;
  }
}
class MinimalView extends View {
  render() {
    return `
    <div>
      <h2>${this.resource.title()}</h2>
      <a href="${this.resource.link()}">readmore</a>
    </div>
    `;
  }
}

class ArtistResource implements IResource {
  artist: any;
  constructor(artist: any) {
    this.artist = artist;
  }

  title() {
    return this.artist.name;
  }
  body() {
    return this.artist.bio;
  }
  image() {
    return this.artist.image;
  }
  link() {
    return this.artist.slug;
  }
}

class SongResource implements IResource {
  song: any;
  constructor(song: any) {
    this.song = song;
  }

  title() {
    return this.song.name;
  }
  body() {
    return this.song.lyrics;
  }
  image() {
    return this.song.coverImage;
  }
  link() {
    return this.song.spotifyLink;
  }
}

const artist = new ArtistResource({
  name: 'Anurag',
  bio: '404 not found',
  image: '/img/mypic.png',
  slug: '/u/anuraghazra',
});
const song = new SongResource({
  name: 'Cant belive i can fly',
  lyrics: 'la la la la la',
  coverImage: '/img/cover.png',
  spotifyLink: '/s/song/132894',
});

const artist_detail_view = new DetailedView(artist);
const artist_minimal_view = new MinimalView(artist);

const song_detail_view = new DetailedView(song);
const song_minimal_view = new MinimalView(song);

console.log(artist_detail_view.render());
console.log(song_detail_view.render());
console.log(artist_minimal_view.render());
console.log(song_minimal_view.render());
Enter fullscreen mode Exit fullscreen mode

第 7 天

  • 复合设计模式

复合模式允许我们组合具有分层树结构的对象。

用例

我所看到的这种模式的很好的用例是,您可以轻松创建可组合的分层和分组系统,就像
photoshop 中有一个 Layer() 类,您将 Circle/Shape 类推送到 Layer,并且这些形状将
相对定位并成为该 Layer 的父级。

const rootLayer = new Layer('rootlayer');
const shapesLayer = new Layer('my layer');
const circle = new Shape(100, 100, 'red');
const box = new Shape(200, 100, 'red');

layer.add(circle);
layer.add(box);
rootLayer.add(shapesLayer);
Enter fullscreen mode Exit fullscreen mode
  • 例子

github 上的代码

正如您所看到的,我有一个 FileNode,FolderNode,如果我要在不使用复合模式的情况下实现它,那么我必须做
额外的检查来查看传递的组件的类型是否是 Folder,然后递归地遍历子节点并制作
整个树。

interface Component {
  remove?: (c: Component) => void;
  add?: (c: Component) => void;
  ls: () => string;
}

class FolderNode implements Component {
  name: string;
  childrens: Component[];
  constructor(name: string) {
    this.name = name;
    this.childrens = [];
  }

  add(component: Component) {
    this.childrens.push(component);
  }

  remove(component: Component) {
    this.childrens = this.childrens.filter((c: Component) => c !== component);
  }

  ls() {
    let str = '\n---' + this.name;
    this.childrens.forEach(child => {
      str += child.ls();
    });
    return str;
  }
}

class FileNode implements Component {
  name: string;
  constructor(name: string) {
    this.name = '\n------' + name;
  }

  ls() {
    return this.name;
  }
}

let root = new FolderNode('root');
let src = new FolderNode('src');
let lib = new FolderNode('lib');

let jsFile = new FileNode('app.js');
let htmlFile = new FileNode('index.html');
let cssFile = new FileNode('style.css');
let mainFile = new FileNode('index.js');

src.add(jsFile);
src.add(htmlFile);
src.add(cssFile);
lib.add(mainFile);

root.add(src);
root.add(lib);

console.log(root.ls());
Enter fullscreen mode Exit fullscreen mode

第 8 天

  • 装饰器模式

装饰器模式允许我们为任何类/对象添加额外的行为,而无需定义任何子类。我
非常喜欢装饰器带来的灵活性和可组合性。

用例

装饰器模式极其有用,我们已经在很多地方用到了它。Angular 开发者
经常使用 @Decorator 语法。React 也使用了高阶函数(装饰器),而像 MobX 这样的库也
巧妙地利用了装饰器模式。

Javascript 将来也会有原生的 @Decorators 支持,@Decorator 提案现在处于“第
2 阶段”,所以我们可能会看到一些变化,我对此感到很兴奋。但我们可以使用 typescript/babel 将它们编译为今天的 js 并立即使用它们。

  • 例子

GitHub 上有更多示例

// simplified example
function loggerDecorator(wrapped) {
  return function(...args) {
    console.log('******');
    console.log(wrapped.apply(this, args));
    console.log('******');
  };
}
function mapper(arr: any[], add: number) {
  return arr.map(i => i + add);
}

loggerDecorator(mapper)([1, 2, 3], 10);
Enter fullscreen mode Exit fullscreen mode

第 9 天

  • 外观模式

外观模式为任何复杂的API/子系统提供了一致、统一的API,使得客户端更容易使用

它基本上作为一个引导程序工作,它抽象出所有复杂的设置并提供一个直接
简单的界面。

  • 例子

示例有点大,所以请在 github 上查看

第 10 天

  • 代理设计模式

代理是一个作为占位符或替代任何其他对象的对象。代理提供了与
原始对象类似的接口,但扩展了对象对变化的反应行为。

代理主要有5种类型。

  • 远程代理
  • 虚拟代理
  • 缓存代理
  • 保护代理
  • 智能代理

▶️远程代理充当两个远程源之间的转换器,您可以执行诸如记录请求之类的操作。

▶️ 缓存代理通过缓存任何长时间运行的操作的结果并提供缓存的结果
而不是每次从原始源请求数据来提高性能。

▶️虚拟代理对象是一个可以延迟启动的默认占位符代理,我们可以将其视为一个骨架
对象,在数据加载之前充当原始对象。

▶️保护代理主要作为原始对象的身份验证层,限制对
对象的未经授权的访问。

▶️ 智能代理为原始对象添加额外的行为,例如将数据发送到任何第三方 API 或
记录数据

  • 例子

gihtub 上有更多示例

// EXAMPLE OF PROTECTION PROXY
interface IServer {
  request(url: string): void;
}
class Server implements IServer {
  request(url: string) {
    console.log('------');
    console.log('loading:    ', url);
    console.log('completed:  ', url);
    console.log('------');
  }
}

class ProtectedServer implements IServer {
  api: IServer;
  bannedWebsites: string[];
  constructor() {
    this.api = new Server();
    this.bannedWebsites = ['https://fakesite.com', 'https://spamming.com', 'https://harmfulsiteyoushouldvisit.com'];
  }
  request(url: string) {
    if (this.bannedWebsites.includes(url)) {
      console.log('------');
      console.log('BANNED:    ', url);
      console.log('------');
    } else {
      this.api.request(url);
    }
  }
}

const server = new ProtectedServer();
console.log('EXAMPLE-1 Protected Proxy');
server.request('https://google.com');
server.request('https://fakesite.com');
server.request('https://facebook.com');
Enter fullscreen mode Exit fullscreen mode

启动行为设计模式

第 11 天

  • 责任链(CoR)

CoR 是一种行为设计模式,我们称之为中间件。CoR 让我们将单个逻辑委托给
处理程序,并将其传递给下一个处理程序。

现实世界类比

一个很好的现实世界类比是呼叫中心或技术支持渠道......当您致电他们时,首先会有
一个自动语音提示您执行一些步骤以便与真人交谈,然后他们会将您的电话转接给
真人,如果他们无法帮助您,他们会再次将您的电话转接给技术人员。

用例

Express.js 大量使用 CoR 或中间件模式,它将 next() 处理程序传递给下一个中间件,执行
一些检查并在其间执行一些操作。

当你希望你的逻辑可复用,并将逻辑委托给多个处理程序时,CoR 会非常有用。CoR 还
可以通过确保每个处理程序块都执行
特定的工作并将数据传递给下一个处理程序,从而最大限度地降低紧耦合系统的复杂性。

  • 例子

github 上的代码

// Chain of responsibility
import { consoleColor } from '../utils';

interface IHandler {
  addMiddleware(h: IHandler): IHandler;
  get(url: string, callback: (data: any) => void): void;
}

abstract class AbstractHandler implements IHandler {
  next: IHandler;
  addMiddleware(h: IHandler) {
    this.next = h;
    return this.next;
  }

  get(url: string, callback: (data: any) => void) {
    if (this.next) {
      return this.next.get(url, callback);
    }
  }
}

class Auth extends AbstractHandler {
  isAuthenticated: boolean;
  constructor(username: string, password: string) {
    super();

    this.isAuthenticated = false;
    if (username === 'anuraghazra' && password === 'password123') {
      this.isAuthenticated = true;
    }
  }

  get(url: string, callback: (data: any) => void) {
    if (this.isAuthenticated) {
      return super.get(url, callback);
    } else {
      throw new Error('Not Authorized');
    }
  }
}

class Logger extends AbstractHandler {
  get(url: string, callback: (data: any) => void) {
    consoleColor('green', '/GET Request to: ', url);
    return super.get(url, callback);
  }
}

class Route extends AbstractHandler {
  url: string;
  URLMaps: {};
  constructor() {
    super();
    this.URLMaps = {
      '/api/todos': [{ title: 'hello' }, { title: 'world' }],
      '/api/random': Math.random(),
    };
  }

  get(url: string, callback: (data: any) => void) {
    super.get(url, callback);

    if (this.URLMaps.hasOwnProperty(url)) {
      callback(this.URLMaps[url]);
    }
  }
}

const route = new Route();
route.addMiddleware(new Auth('anuraghazra', 'password123')).addMiddleware(new Logger());

route.get('/api/todos', data => {
  consoleColor('blue', JSON.stringify({ data }, null, 2));
});
route.get('/api/random', data => {
  console.log(data);
});
Enter fullscreen mode Exit fullscreen mode

第 12 天

  • 命令模式

命令模式是一种行为设计模式,它让我们将业务逻辑与客户端
实现分离。

现实世界类比

想象一下,当您去餐馆时,您打电话给服务员并命令他下订单,服务员将
命令传递给主管,主管完成订单后再将命令返回给您。

用例

命令模式还允许你执行撤消和重做操作。假设你正在开发一个文本编辑器,并且想要
实现撤消和重做功能,那么命令模式就非常有用。命令模式还提供了一个良好的接口来实现
模块化的 GUI 操作,使我们能够将 UI 层与代码逻辑分离。

传统上,如果您具有 CopyText 功能,您可能会面临这样的情况,当您想允许用户从 ContextMenu 和 Toolbar 触发该
CopyText 功能时,在这种情况下,命令模式非常有用。

  • 例子

查看 github 上的代码

interface ICommand {
  undo?(payload?: any): any;
  execute(payload?: any): any;
}

abstract class Command implements ICommand {
  calc: Calculator;
  constructor(calc?: Calculator) {
    this.calc = calc;
  }
  execute() {}
}

class Calculator {
  currentValue: number;
  history: CommandHistory;
  constructor() {
    this.history = new CommandHistory();
    this.currentValue = 0;
  }

  getValue(): number {
    return this.currentValue;
  }

  execute(command: ICommand) {
    this.currentValue = command.execute(this.currentValue);
    this.history.add(command);
  }

  undo() {
    let lastCommand = this.history.remove();
    if (lastCommand) {
      this.currentValue = lastCommand.undo(this.currentValue);
    }
  }
}

class CommandHistory {
  commands: ICommand[];
  constructor() {
    this.commands = [];
  }

  add(command: ICommand) {
    this.commands.push(command);
  }

  remove() {
    return this.commands.pop();
  }
}

class AddCommand {
  value: number;
  constructor(value: number) {
    this.value = value;
  }
  execute(value: number) {
    return value + this.value;
  }
  undo(value: number) {
    return value - this.value;
  }
}

const calc = new Calculator();
calc.execute(new AddCommand(50));
calc.undo(); // undo last command
Enter fullscreen mode Exit fullscreen mode

第 13 天

  • 迭代器模式

迭代器模式是一种行为设计模式,它让我们遍历任何复杂的数据结构,而无需
向客户端公开底层实现。

用例

我们可以轻松地使用迭代器模式遍历图、列表和树。JavaScript 内部使用迭代器协议来
实现 [...spread] 扩展运算符和循环。

  • 例子

github 上的代码

interface IIterator {
  next(): any;
  hasMore(): any;
}

interface ICounter {
  getIterator(): IIterator;
}

class Counter implements ICounter {
  collection: any;
  constructor(data: any) {
    this.collection = data;
  }
  getIterator() {
    return new CounterIterator(this.collection);
  }
}

class CounterIterator implements IIterator {
  current: number;
  collection: any;
  constructor(data: any) {
    this.collection = data;
    this.current = 0;
  }

  next() {
    return this.collection[this.current++];
  }

  prev() {
    return this.collection[this.current - 1];
  }

  hasMore() {
    return this.collection.length > this.current;
  }
}

let iterator = new Counter([1, 2, 3, 4, 5]).getIterator();
while (iterator.hasMore()) {
  console.log(iterator.next());
}
Enter fullscreen mode Exit fullscreen mode

第 14 天

  • 中介者设计模式

中介设计是一种行为设计模式,它决定了对象集合如何相互作用。
中介模式鼓励组件之间的松散耦合,因为它可以防止对象直接相互引用
,从而降低整体复杂性。

中介者充当不同对象之间的中间人,所有其他对象
仅通过中介者进行通信。

现实世界类比

一个很好的现实世界类比是空中交通管制员。虽然飞机起飞和降落时不会
直接对话,但它们会与空中交通管制员沟通,获取其他飞机的信息,然后由控制
塔告知它们何时起飞/降落。

用例

我认为这种模式有一些用例,例如在构建聊天室时,您可以实现中介模式来
简化聊天室不同成员之间的关系并通过中介向他们发送消息。

您还可以使用中介模式作为前端应用程序中的全局事件管理器,其中组件
通过中介相互通信而不是传递回调/道具。

  • 例子

github 上的代码

// mediator pattern
import { consoleColor } from '../utils';

interface IMediator {
  sendMessage(msg: string, from: any, to?: any): void;
}

class Chatroom implements IMediator {
  members: { [x: string]: Member };
  constructor() {
    this.members = {};
  }

  addMember(member: Member) {
    member.chatroom = this;
    this.members[member.name] = member;
  }

  sendMessage(msg: string, from: Member, to?: Member) {
    Object.keys(this.members).forEach(name => {
      if (!to && name !== from.name) {
        this.members[name].receive(msg, from);
        return;
      }
      if (to && name == to.name) {
        this.members[name].receive(msg, from);
      }
    });
  }
}

class Member {
  name: string;
  chatroom: Chatroom;
  constructor(name: string) {
    this.name = name;
    this.chatroom = null;
  }

  send(msg: string, to?: any) {
    this.chatroom.sendMessage(msg, this, to);
  }

  receive(msg: string, from: Member) {
    consoleColor('magenta', `-------`);
    consoleColor('cyan', `${from.name} says to ${this.name} : `);
    consoleColor('green', `${msg}`);
    consoleColor('magenta', `-------`);
  }
}

const chatroom = new Chatroom();

let anurag = new Member('Anurag');
let hitman = new Member('hitman');
let jonathan = new Member('John Wick');
chatroom.addMember(anurag);
chatroom.addMember(hitman);
chatroom.addMember(jonathan);

anurag.send("I'm more dangerous than you hitman");
hitman.send('Sorry brother forgive me! pls', anurag);
jonathan.send('Hey hey hey hitman, nerver ever mess with Anurag', hitman);
Enter fullscreen mode Exit fullscreen mode

第15天

  • 观察者设计模式

观察者设计模式是一种行为设计模式,它是一种订阅系统,可以通知多个对象
它们正在观察的对象的任何变化。

▶️观察者模式的妙处在于它将状态与实际业务逻辑分离。就 UI 而言,
您可以将状态与 UI 的实际渲染分开,如果状态更新,UI 将自动
对其做出反应。

假设你的状态中有一些 Todo 对象,你可以将数据与 UI 解耦,并
完全不同地实现渲染逻辑。你可以使用 DOMRenderer 和 ConsoleRenderer,它们都会对
Todo 对象的更改做出响应并进行更新。这里有一个很好的例子:https://github.com/anuraghazra/VanillaMVC

现实世界类比

您可以将观察者模式与每日报纸订阅进行比较,如果您订阅任何报纸,您不需要
每天去商店购买报纸,而是由出版商将报纸发送到您的家中。

另一个类比是 YouTube,你可能很清楚,订阅 YouTube 频道意味着你会
收到新视频的通知。观察者模式也类似。作为用户,你会订阅你
选择接收通知的事件。

用例

观察者模式有很多用例。(非常多)Vuejs 的反应系统依赖于观察者模式。RxJs 的整个理念
都基于观察者。MobX 也有效地运用了观察者设计模式。


从用户界面到数据反应性,当特定对象中的某些更改/事件必须反映在其他对象上时,观察者模式非常方便

  • 例子

github 上的代码

import { consoleColor } from '../utils';

interface IPublisher {
  addSubscriber(subscriber: any): void;
  removeSubscriber(subscriber: any): void;
  notifyObservers(): void;
}
interface IObserver {
  notify(payload: any): void;
}

class Publisher implements IPublisher {
  subscribers: IObserver[];
  state: any;
  constructor(state: any = {}) {
    this.subscribers = [];
    this.state = state;
  }

  addSubscriber(subscriber: IObserver) {
    if (this.subscribers.includes(subscriber)) return;
    this.subscribers.push(subscriber);
  }

  removeSubscriber(subscriber: IObserver) {
    if (!this.subscribers.includes(subscriber)) return;
    let index = this.subscribers.indexOf(subscriber);
    this.subscribers.splice(index, 1);
  }

  notifyObservers() {
    this.subscribers.forEach(subs => {
      subs.notify(this.state);
    });
  }

  setState(newState: any) {
    this.state = newState;
    this.notifyObservers();
  }
}

class UserInterface implements IObserver {
  renderTodos(todos) {
    console.clear();
    todos.forEach(todo => {
      consoleColor('cyan', '-----');
      consoleColor(todo.isCompleted ? 'green' : 'red', `${todo.title} ${todo.isCompleted ? '[DONE]' : '[PENDING]'}`);
      consoleColor('cyan', '-----');
    });
  }

  notify(state: any) {
    this.renderTodos(state.todos);
  }
}

const store = new Publisher({
  todos: [
    { title: 'hello', isCompleted: false, id: 1 },
    { title: 'world', isCompleted: false, id: 2 },
  ],
});

const userInterface = new UserInterface();
store.addSubscriber(userInterface);

// add todo
store.setState({
  todos: [...store.state.todos, { title: 'new item', id: Math.random() }],
});

// remove todo
store.setState({
  todos: store.state.todos.filter(t => t.id !== 2),
});
Enter fullscreen mode Exit fullscreen mode

第16天

  • 状态模式

状态模式是一种行为设计模式,它允许对象根据其内部状态改变其行为。

如果你想看代码,我的 github repo 中有三个关于状态模式的示例:
https://github.com/anuraghazra/design-patterns-everyday

▶️状态模式可以与状态机相关联,在某个时间点,应用程序只能处于一种
状态或有限数量的状态。

状态机严重依赖 if 语句和 switch case,
当代码库变大时,会导致逻辑复杂且代码难以维护。状态模式会根据当前状态改变行为方法。

现实世界类比

假设你有一个音乐播放器,并且该音乐播放器有两个按钮“向上”和“向下”

  • 当您播放歌曲时,“UP”和“DOWN”按钮将改变歌曲音量。
  • 当您在播放列表菜单上时,“向上”和“向下”按钮将在列表中上下滚动。

用例

一个好的实际用例是任何绘图应用程序/文本编辑器或任何具有某些类并
根据某些状态改变其行为的应用程序。

例如:如果正在构建一个绘图应用程序,该应用程序将有一个画笔工具,该工具将
根据所选的颜色/大小绘制不同的颜色/大小。

另一个例子是文本编辑器,其中你有一个用于在屏幕上写入文本的类,但根据
大写/粗体/小写按钮,你可以在屏幕上写入适当的字符

  • 例子

github 上的代码

/* SIMPLE TOGGLE */
interface IToggleState {
  toggle(state: IToggleState): void;
}

class ToggleContext {
  currentState: any;
  constructor() {
    this.currentState = new Off();
  }

  setState(state: IToggleState) {
    this.currentState = state;
  }

  toggle() {
    this.currentState.toggle(this);
  }
}

class Off implements IToggleState {
  toggle(ctx: ToggleContext) {
    console.log('OFF');
    ctx.setState(new On());
  }
}
class On implements IToggleState {
  toggle(ctx: ToggleContext) {
    console.log('ON');
    ctx.setState(new Off());
  }
}

let button = new ToggleContext();
button.toggle();
button.toggle();
Enter fullscreen mode Exit fullscreen mode

第17天

  • 策略设计模式

策略设计模式是一种行为设计模式,它让我们为执行特定
操作定义不同的算法,并根据需要交换它们。基本上意味着您可以在不同类型的行为和
实现之间切换。

策略设计模式与状态设计模式非常相似。策略模式是状态模式的扩展,但
策略模式完全使子类彼此独立。

现实世界类比

我认为现实世界中一个很好的类比是一场足球比赛。教练(上下文)决定

策略是游戏过程中遇到的每种情况,并根据情况在策略之间切换。例如,如果
对手正在防守,那么教练就会将策略改为进攻。当球队领先 1 个
球时,教练会将策略改为半防守。

用例

如果您曾经使用过passportjs,那么您已经使用了策略设计模式。passportjs使用策略模式轻松
更改/添加新的身份验证提供程序,并且系统变得更加灵活,易于使用和扩展。

  • 例子

github 上的代码

// Strategy pattern
interface IStrategy {
  authenticate(...args: any): any;
}

class Authenticator {
  strategy: any;
  constructor() {
    this.strategy = null;
  }

  setStrategy(strategy: any) {
    this.strategy = strategy;
  }

  authenticate(...args: any) {
    if (!this.strategy) {
      console.log('No Authentication strategy provided');
      return;
    }
    return this.strategy.authenticate(...args);
  }
}

class GoogleStrategy implements IStrategy {
  authenticate(googleToken: string) {
    if (googleToken !== '12345') {
      console.log('Invalid Google User');
      return;
    }
    console.log('Authenticated with Google');
  }
}

class LocalStrategy implements IStrategy {
  authenticate(username: string, password: string) {
    if (username !== 'johnwick' && password !== 'gunslotsofguns') {
      console.log('Invalid user. you are `Excommunicado`');
      return;
    }
    console.log('Authenticated as Baba Yaga');
  }
}

const auth = new Authenticator();

auth.setStrategy(new GoogleStrategy());
auth.authenticate('invalidpass');

auth.setStrategy(new GoogleStrategy());
auth.authenticate('12345');

auth.setStrategy(new LocalStrategy());
auth.authenticate('anurag', '12345');

auth.setStrategy(new LocalStrategy());
auth.authenticate('johnwick', 'gunslotsofguns');
Enter fullscreen mode Exit fullscreen mode

第18天

  • 模板方法

模板方法是一种行为设计模式,它逐步定义算法的骨架并让
子类覆盖它们。

模板方法的作用基本上是,它要求您将算法拆分成更小的块并
为它们创建单独的方法,然后按顺序调用每个方法。这样,您就可以
在子类中覆盖算法的任何步骤。

现实世界类比

一个很好的现实世界类比是房屋建筑的概念,建造房屋需要一些步骤,例如
建造屋顶、地板、墙壁、电力供应等,客户(或业主)可以定制这些
组件并获得不同类型的房屋。

用例

模板方法在框架中被广泛使用,让我们以 Reactjs 为例

React 的 Class Component 是一个实现模板方法,其中它具有 componentDidMount、componentWillUnmount 等占位符方法
,客户端将根据他们的需要覆盖和定制这些方法。

顺便说一句,有趣的是,这种控制反转被称为“好莱坞原则”(“不要打电话给我们,我们会打电话给你”。)

  • 例子

github 上的代码

// template method
import fs from 'fs';

abstract class DataParser {
  data: string;
  out: any;
  constructor() {
    this.data = '';
    this.out = null;
  }

  parse(pathUrl: string) {
    this.readFile(pathUrl);
    this.doParsing();
    this.postCalculations();
    this.printData();
  }

  readFile(pathUrl: string) {
    this.data = fs.readFileSync(pathUrl, 'utf8');
  }

  doParsing() {}
  postCalculations() {}
  printData() {
    console.log(this.out);
  }
}

class DateParser extends DataParser {
  doParsing() {
    let dateRegx = /(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}/gim;
    this.out = this.data.match(dateRegx);
  }
}

class CSVParser extends DataParser {
  doParsing() {
    this.out = this.data.split(',');
  }
}

class MarkupParser extends DataParser {
  doParsing() {
    this.out = this.data.match(/<\w+>.*<\/\w+>/gim);
  }

  postCalculations() {
    this.out = this.out.reverse();
  }
}

const dataUrl = '../../behavioral/data.csv';

new DateParser().parse(dataUrl);
new CSVParser().parse(dataUrl);
new MarkupParser().parse(dataUrl);
Enter fullscreen mode Exit fullscreen mode

第19天

  • 访客模式

访客设计模式是一种行为设计模式,它允许您定义新的操作/行为而无需更改
类。

现实世界类比

refactoring.guru 给出了一个很好的现实世界类比,它说想象一个渴望获得新
客户的保险代理人,他将访问该地区的每一栋建筑,

  • 如果是住宅楼,他就卖医疗保险,
  • 如果是银行,他会出售盗窃保险。
  • 如果是商店,他会销售火灾和洪水保险。

用例

当需要扩展现有行为而不改变基类时,访问者模式非常有用。

如果您曾经写过 GraphQL 指令,那么您就使用过访问者模式。

GraphQL 服务器公开一个“SchemaDirectiveVisitor”类,该类具有“visitFieldDefinition”和“visitEnumValue”等方法,
它实现了访问者模式,为模式添加额外的行为。

访问者模式对于修改 AST 树也非常有用,你可以访问每个节点并逐个修改它。
我的 git repo 上有一个例子:https://github.com/anuraghazra/design-patterns-everyday

您还可以实现访问者模式来制作导出器,正如我的示例所见。我有一个 SVGExporter 和
CanvasCallsExporter。

  • 例子

查看 github 上的示例


就是这样!呼!好长啊……我知道你可能没看过,不过没关系
, 当你对某个设计模式感到困惑或者遇到困难时,可以随时回来。

我个人认为在 Web 开发领域中最有用的模式是:

  • 观察者
  • 游客
  • 迭代器
  • 责任链
  • 战略
  • 代理人
  • 装饰器

链接:

学习资源:

希望这篇文章对你有帮助!感谢大家的阅读。

文章来源:https://dev.to/anuraghazra/design-patterns-everyday-53m0
PREV
我在 Razorpay 的前端面试经历
NEXT
使用 HTML 创建可重用的 Web 组件