三种小型创建模式 - 设计模式简介

2025-05-25

三种小型创建模式 - 设计模式简介

更新:根据评论部分的反馈,几个代码示例已经更新。

设计模式对于程序员来说至关重要,至少对我来说是这样。这些是针对常见问题的成熟解决方案,有助于保持代码的可维护性和松耦合性。了解得越多,就越容易解决我们面临的所有问题。

一旦你学会了它们,你就会随处可见。我用适配器连接 API,用单例来管理 Ember 服务,用状态和观察者来管理 UI,用外观来管理对象,也用来在节假日里捉弄我的亲戚。

然而,作为一个没有计算机科学背景的人,学习这些模式真是费劲。每本解释或书籍都充斥着各种专业术语,让我费解。有些术语我至今仍不太明白。我几乎不知道享元模式是怎么运作的,任何说知道的人都是骗子。

所以我的想法是用故事来解释设计模式。故事本身也已经很成熟,能够解决常见问题,但更容易理解。那就是童话故事!

以童话的方式介绍设计模式

本系列旨在帮助人们学习设计模式的基本功能和用法。这些示例是基础的,可以作为进一步学习的起点。它们也使用 JavaScript 编写,但其理念适用于任何面向对象语言。

这里我将介绍五种创建型设计模式。这些模式用于创建和管理更易于维护且副作用更少的对象。

说到创造,我就想到了三只小猪!我们开始吧。

用工厂建造稻草房

在这个版本的童话故事中,假设每只小猪都想进军房地产市场。第一只小猪自然而然地用稻草搭建了房子。他决定用 JavaScript 类来制作这些房子。

小猪编写了这个用于建造稻草屋的基类。它唯一需要的参数是所需的稻草捆数量。

class StrawHouse {
  constructor(straw) {
    this.straw = straw;
  }
}
Enter fullscreen mode Exit fullscreen mode

随着订单的到来,猪意识到这个班次建造房屋效率不高。顾客会告诉他他们想要的房子高度,以及是否需要用更多稻草加固。猪每次都要计算,有时同样的操作还要重复多次。

以这种方式创建这么多房屋实例非常累人,因此小猪使用工厂模式来管理创建每个房屋实例的工作。

class StrawHouseFactory {
  static create(height, specs) {
    const strawBase = (specs === 'reinforced') ? 350 : 150,
          amountOfStraw = height * strawBase;

    return new StrawHouse(amountOfStraw);
  }
}
Enter fullscreen mode Exit fullscreen mode

这个稻草屋工厂让他能够根据客户提供的信息快速创建不同的稻草屋,进行计算并返回所需的StrawHouse类实例。这是一个简单却强大的优势,这很合理,因为工厂可以说是最简单的创建型模式。

const smallStrawHouse = StrawHouseFactory.create(10),
      strongStrawHouse = StrawHouseFactory.create(25, 'reinforced');
Enter fullscreen mode Exit fullscreen mode

额外的好处是,房屋类和工厂类并没有耦合得太紧密。他可以相当轻松地修改其中一个类,而不会破坏另一个类。这是在其他创建型模式,乃至所有设计模式中都能看到的共同优势。

用原型建造木屋

第二只小猪正在建造木屋,但他面临着一个不同的问题。他所有的客户都希望社区里的木屋都看起来相似。他可以尝试他哥哥建造稻草屋时使用的工厂方法,但他会一遍又一遍地建造同一种类型的木屋。编写代码来生成相同的实例非常重复,他想要更高效的方案。

第二只小猪则使用了原型模式。假设一组客户都想要一栋小木屋。他不会为他们制作多个木屋实例,而是只制作一个(原型),并包含一个根据需要复制它的方法。

class StickHouse {
  constructor(height, sticks) {
    this.height = height;
    this.sticks = sticks;
  }
}

class StickHousePrototype {
  constructor(height) {
    this.height = height;
    this.sticks = height * 100;
  }

  copy() {
    return new StickHouse(this.sticks, this.height);
  }
}
Enter fullscreen mode Exit fullscreen mode

这样可以减少重复,并确保每份副本完全相同。他甚至可以对每份副本进行修改,以增加灵活性。

const smallStickHouse = new StickHousePrototype(15),
      largeStickHouse = new StickHousePrototype(50);

const housesForFriends = {
  'Amy': smallStickHouse.copy(),
  'Bob': smallStickHouse.copy(),
  'Cole': smallStickHouse.copy(),

  'Dingus': largeStickHouse.copy(),
  'Eragon': largeStickHouse.copy()
}
Enter fullscreen mode Exit fullscreen mode

总体而言,如果您需要大量完全相同或大部分相同的实例,则此模式最有效。

与建筑商一起建造砖房

第三只小猪正在销售砖房,这是一个更宏伟的目标。砖房的建造过程比较复杂,需要几个步骤。此外,由于砖房是森林里的新手,客户经常会拖到一半才决定房子的大小。第三只小猪意识到,他需要一个能够处理所有这些额外复杂性而不会不知所措的模型。

他偶然发现了建造者模式,他的小猪祈祷得到了回应!

这是第三只小猪建造砖房的基础课程。它需要三个尺寸以及将砖块粘在一起所需的水泥量。

class BrickHouse {
  constructor(width, length, height, cement) {
    this.width = width;
    this.length = length;
    this.height = height;
    this.cement = cement;
  }
}
Enter fullscreen mode Exit fullscreen mode

然后,小猪编写了他的 Builder 类。他主要想从中得到以下东西:

  • 用于设置每个尺寸的单独方法。构建器应该能够执行一些操作,暂停以运行其他代码,然后从中断处继续。
  • 一旦知道完整尺寸,就可以计算需要多少水泥的方法。
  • 最后一种方法是获取所有这些信息并归还完工的砖房。
class BrickHouseBuilder {
  setWidth(width) {
    this.width = width;
    return this;
  }

  setLength(length) {
    this.length = length;
    return this;
  }

  setHeight(height) {
    this.height = height;
    return this;
  }

  addCement() {
    this.cement = this.getCementBase() + this.getCementForBetweenBricks();
    return this;
  }

  getFloorSize() {
    return this.width * this.length;
  }

  getCementForBetweenBricks() {
    return this.getFloorSize() * 0.25;
  }

  getCementBase() {
    return this.getFloorSize() * (this.height / 5);
  }

  build() {
    return new BrickHouse(this.width, this.height, this.length, this.cement);
  }
}
Enter fullscreen mode Exit fullscreen mode

我知道这很难理解。但我知道,猪可以调用每个方法,用它需要的任何数据,精心建造每栋房子。

const newBrickHouse = new BrickHouseBuilder(),
      smallBrickHouse = newBrickHouse.setWidth(5).setLength(5).setHeight(5).addCement().build();
Enter fullscreen mode Exit fullscreen mode

当他必须暂停代码构建时它也能发挥作用。

const largeBrickHouse = newBrickHouse.setWidth(20).setLength(25);

// Extra calculations here as the client decides what to do

largeBrickHouse.setHeight(20).addCement().build();
Enter fullscreen mode Exit fullscreen mode

正如您所见,构建器非常适合抽象出创建更大对象和多个语句所需的更复杂的步骤和计算。

使用单例模式构建业务

让我们把这个童话故事快进到未来。三只小猪遇到了大灰狼,他们一起成立了一家名为“猪与狼合伙房地产有限责任公司”的房地产公司。这是一家很棒的新公司,他们想用 JavaScript 来管理公司信息。

三只小猪意识到他们不能使用之前的任何模式,因为这些模式是为一个类的多个实例设计的。他们的公司只有一个实例,因此这个类也只有一个实例。否则,那些想成为房地产大佬的家伙可能会试图抄袭,并夺走他们毕生的心血!

这就是单例的魅力所在。单例的设置意味着只能创建一个实例。任何创建新实例的尝试都会返回到原始实例。

class RealEstateCompany {
  constructor(employees) {
    if (typeof RealEstateCompany.instance === 'object') {
      return RealEstateCompany.instance;
    }
    RealEstateCompany.instance = this;

    this.employees = employees;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

下面你会看到有人试图制造该公司的两个实例,第二个实例是假的,使用了不同的其他野生动物。

const company = new RealEstateCompany(['Pig 1', 'Pig 2', 'Pig 3', 'Wolf']),
      fakeCompany = new RealEstateCompany(['Zebra', 'Aardvark', 'Chris Pratt'])
Enter fullscreen mode Exit fullscreen mode

由于它是单例,fakeCompany所以返回的值与 相同company。这样,猪就可以在程序的任何地方引用它们的真实公司,并获取原始实例,包括在其他地方对其进行的任何更改。合适的单例是可靠的“单一事实来源”。

如果您想了解更多,我在这里写了有关更实用且与 Pokemon 相关的 Singleton 用法的文章。

利用抽象工厂接管房地产市场

属猪人拥有一切。他们在市场上拥有三种类型的房产,拥有一家公司,每个人都有私人游泳池,还有希望将来能结婚的固定女友。此外,他们还拥有大规模的混乱。

为了建造所有的房子,猪需要管理许多创造模式:

  • 稻草屋工厂
  • 棍棒房屋原型制作者
  • 砖房建造者。

值得庆幸的是,还有最后一种创建模式来管理它们:抽象工厂!

常规工厂只会创建单个类的实例,而抽象工厂则会同时创建多个类的实例。猪需要管理的不是一个,而是三个。抽象工厂可以调用任何它们需要的类,甚至可以添加一些额外的逻辑来覆盖常见的用例。

class PigHouseAbstractFactory {
  static strawHouse(size) {
    if (size === 'large') {
      StrawHouseFactory.create(25, true);
    } else {
      StrawHouseFactory.create(10)
    }
  }

  static stickHouse(size) {
    if (size === 'large') {
      return new newStickHousePrototype(50).copy();
    } else {
      return new newStickHousePrototype(15).copy();
    }
  }

  static brickHouse(size) {
    if (size === 'large') {
      return new BrickHouseBuilder.width(20).length(25).height(20).height(20).getCement().build();
    } else {
      return new BrickHouseBuilder.width(5).length(5).height(5).getCement().build();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

这种模式允许他们从单个类开始填写任何订单,并且不需要将任何依赖类耦合得太紧密。

const smallStrawHouse = PigHouseAbstractFactory.strawHouse(),
      largeStrawHouse = PigHouseAbstractFactory.strawHouse('large'),
      smallStickHouse = PigHouseAbstractFactory.stickHouse(),
      largeStickHouse = PigHouseAbstractFactory.stickHouse('large'),
      smallBrickHouse = PigHouseAbstractFactory.brickHouse(),
      largeBrickHouse = PigHouseAbstractFactory.brickHouse('large');
Enter fullscreen mode Exit fullscreen mode

看起来,小猪们在这个童话故事中有一个幸福的结局,并且在房地产领域拥有光明的未来。

设计模式探索才刚刚开始

我希望在本系列中涵盖所有 23 种经典的“四人帮”设计模式。这些文章并非你需要了解的全部,但我希望它们能为学习每种模式的复杂性提供简单的基础。我在学习这些模式时很难找到适合初学者的入门资料,希望这些文章能帮助其他人避免同样的困境。

待续...

封面图片由 SafeBooru.org 提供

文章来源:https://dev.to/maxwell_dev/the- Three-little-creational-patterns-a-design-patterns-intro-1dmn
PREV
我希望拥有的 Web 可访问性介绍
NEXT
我希望拥有的 Shell 简介