JS设计模式-构造器模式与工厂模式

2,201 阅读10分钟

前言

📢 博客首发 : 阿宽的博客

为什么自己会有这个专题的文章,因为想提高自己,也想帮助其他跟我一样的前端渣渣,一开始的时候,对JS设计模式,真的是觉得很难,问过别人,你的项目中用了设计模式吗,他说有啊,我说能让我看看吗,他告诉我,设计模式是一种思想,这个我也不好说,你得去看看书,看看一些优秀项目的源码,看看别人是怎么设计的。

于是,我就踏上了不归之路,学习就是这样,得靠驱动力,我的驱动力就是,这玩意我不会,妈的,装不了逼,我得学,好在群里装逼;好的,学就是了...

以下知识来自《JavaScript 设计模式》《JavaScript 设计模式核⼼原理与应⽤实践》,我是看懂之后,然后自己总结 ~ 当然也会记录书中的一些知识点,直接摘抄下来了,关于小册,如用到其中的文字,也会表明出处。

首先,❗ 设计模式是一种思想

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式的五大基本原则:

  • 单一功能原则(Single Responsibility Principle)
  • 开放封闭原则(Opened Closed Principle)
  • 里式替换原则(Liskov Substitution Principle)
  • 接口隔离原则(Interface Segregation Principle)
  • 依赖反转原则(Dependency Inversion Principle)

在 JS 设计模式中,最核心的思想——封装变化,怎么理解,比如我们写一个东西,这个东西在初始 v1.0.0 的时候是这 diao 样,到了 v10.0 甚至 v100.0 还是这 diao 样,不接受迭代和优化,ojbk,你爱怎么写就怎么写,你只要实现就可以了。 ------来自《JavaScript 设计模式核⼼原理与应⽤实践》掘金小册

but !!!现实中,产品一定会提新需求,一定要做版本迭代,一定要升级优化,所以说,不改变的代码时不存在的 ~~~

我们能做的只有将这个变化造成的影响最小化 —— 将变与不变分离,确保变化的部分灵活、不变的部分稳定。 ------来自《JavaScript 设计模式核⼼原理与应⽤实践》掘金小册

记住 : 设计模式的核心操作是去观察你整个逻辑里面的变与不变,然后将变与不变分离,达到使变化的部分灵活、不变的地方稳定的目的

构造器模式

在面向对象编程中,构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。在 JavaScript 中几乎所有的东西都是对象

Object 构造器用于创建特定类型的对象————准备好对象以备使用,同时接收构造器可以使用的参数,以在第一次创建对象时,设置成员属性的方法的值

对象创建

创建对象的三种基本方式

// 第一种
let obj = {};

// 第二种
let obj = Object.create(null);

// 第三种
let obj = new Object();

看着《Javascript 设计模式》这本书,讲得比较理论,其实构造器模式真的是天天都在用了,举个例子

双十一过了,小彭吐槽小何没有女朋友,作为一名程序狗,岂能让人看不起,我没对象,我还不能自己 new 一个对象出来?

于是,小何洋洋洒洒的在 VS Code 中写下了这段代码 :

const myGirlFriend = {
  name: "小何女朋友",
  age: 18,
  cup: "36D",
  hobby: "吃鸡,王者"
};

过了两天,小何的基友小谭跑过来要小何也帮他 new 一个女朋友,小何心想,这特么不简单吗,问: “你女朋友想找多大的,什么 cup,爱好是啥” 于是,小何很神气的又写下了这段代码

const tanGirlFriend = {
  name: "小谭女朋友",
  age: 22,
  cup: "36E",
  hobby: "旅行,爬山"
};

让小何意想不到的是,他居然火了,很多单身同胞都来找他,要他帮忙 new 一个对象

于是陆陆续续来人,小何一看,妈呀,整个班的人都来了,30 号人,小何告诉自己,我可以,我行 !

但是意想不到的是,出名到整个学院了,几百上千号人都来,要死要死,我要写啥时候,于是,他急中生智,用了构造函数

于是小何写出了一个可以自动创建女朋友的函数:

function GirlFriend(name, age, cup, hobby) {
  this.name = name;
  this.age = age;
  this.cup = cup;
  this.hobby = hobby;

  this.toCopyright = function() {
    console.log(`我是被小何创造出来的对象,我叫${this.name}`);
  };
}

然后呢,小何再也不需要手写字面量了,每次来一个人,我就调用一次

const daiGirl = new GirlFriend("小戴女朋友", 20, "36F", "机车");
const xieGirl = new GirlFriend("小谢女朋友", 19, "36C", "看书");

GirlFriend 这样当新建对象的内存被分配后,用来初始化该对象的特殊函数,就叫做构造器。

用构造器去初始化对象,这就是构造器模式,牛逼!

带原型的构造器

上边的代码,我们来看看,会有什么问题

首先继承上会变得困难,其次是,小何想,像 this.toCopyright() 这样的函数,应该在所有的GirlFriend类型实例之间共享。(这是我的版权!!!)

JavaScript 中函数有一个 prototype 的属性。当我们调用 JavaScript 的构造器创建一个对象时,新对象就会具有构造器原型的所有属性。

稳啊,太强了!

function GirlFriend(name, age, cup, hobby) {
  this.name = name;
  this.age = age;
  this.cup = cup;
  this.hobby = hobby;
}

GirlFriend.prototype.toCopyright = function() {
  console.log(`我是被小何创造出来的对象,我叫${this.name}`);
};

现在 toCopyright()的单一实例就能在所有GirlFriend对象之间共享

工厂模式

简单工厂

小何可轻松了,在使用构造模式造福了整个学院的单身狗之后,突然有一天,小谭来找小何

小谭 : “何总啊,你给我 new 的这个女朋友,我就只知道她的一丢丢信息,但是我不知道她哪的人,你能不能给我 new 一个重庆的小姐姐啊,我喜欢吃辣的妹子 ~”

小谢在旁边听到了,忙着说我也要,我想找一个广州的小姐姐,我比较喜欢吃口味清淡的妹子!

小何一想,我日,你们两要求还挺多。

完了,前边的女朋友共性都被拆离了,本来规定就只有 name``agecuphobby 的,现在居然还要出生地,哈卖批!

小谭 : “我可以给你发个红包~”

小谢 : “我现在就给你发”

小何一听,干了,有钱能使鬼推磨,不就是多一个字段嘛

function ChongQingGirlFriend(name, age, cup, hobby) {
  this.name = name
  this.age = age
  this.cup = cup
  this.hobby = hobby
  this.birthplace = '重庆'
}

function GuangZhouGirlFriend(name, age, cup, hobby) {
  this.name = name
  this.age = age
  this.cup = cup
  this.hobby = hobby
  this.birthplace = '广州'
}

哦吼,小何没想到,这特么的,大家一听到可以自定义出生地的女朋友,直接疯狂找他,红包拆到手软

当小何从重庆写到广州,再从广州写到北京上海成都... 他突然意思到,这量有点大啊

我得写到啥时候,算了,我用工厂模式吧 ~

function FactoryGirlFriend(name, age, cup, hobby, birthplace) {
  if (birthplace === "chongqing") {
    return new ChongQingGirlFriend(name, age, cup, hobby);
  }
  if (birthplace === "chengdu") {
    return new GuangZhouGirlFriend(name, age, cup, hobby);
  }
  if (birthplace === "beijing") {
    return new BeiJingGirlFriend(name, age, cup, hobby);
  }
  if (birthplace === "shanghai") {
    return new ShangHaiGirlFriend(name, age, cup, hobby);
  }
  ...
}

真好,但是我国共划分为 23 个省、5 个自治区、4 个直辖市、2 个特别行政区,要划分再细一些,想想这个 if 语句有些庞大?

小何心想,我总不能,写 n 个城市女朋友吧,这特么得写到啥时候,于是小何再次封装,此时只需要两个构造器就能完成

function GirlFriend(name, age, cup, hobby, birthplace, eatType) {
  this.name = name;
  this.age = age;
  this.cup = cup;
  this.hobby = hobby;
  this.birthplace = birthplace;
  this.eatType = eatType
}

function FactoryGirlFriend(name, age, cup, hobby, birthplace) {
    let eatType = '';
    switch(birthplace) {
        case 'chongqing':
            eatType = '吃辣,辣的一匹';
            break;
        case 'guangzhou':
            eatType = '吃清淡,喝粥喝茶';
            break;
        case 'fuzhou':
            eatType = '沙县,闽南菜';
            break;
        ....
    }
    return new GirlFriend(name, age, cup, hobby, birthplace, eatType);
}

真香啊,工厂模式大法就是好 ~

👉 那么我们该什么时候使用工厂模式?

  • 对象或者组件设置涉及到高程度级别的复杂度时。
  • 根据我们所在的环境方便的生成不同对象的实体时。
  • 在许多共享同一个属性的许多小型对象或组件上工作时。
  • 当带有其它仅仅需要满足一种 API 约定(又名鸭式类型)的对象的组合对象工作时.这对于解耦来说是有用的。

可能这里有人会觉得,我直接这么写不也可以?

function FactoryGirlFriend(name, age, cup, hobby, birthplace) {
    this.name = name;
    this.age = age;
    this.cup = cup;
    this.hobby = hobby;
    this.birthplace = birthplace;

    let eatType = '';
    switch(birthplace) {
        case 'chongqing':
            this.eatType = '吃辣,辣的一匹';
            break;
        case 'guangzhou':
            this.eatType = '吃清淡,喝茶喝粥';
            break;
        case 'fuzhou':
            this.eatType = '沙县,闽南菜';
            break;
        ....
    }
}

ojbk 啊,你这么写又没错,最终效果是一样的,但是思想是不一样的,所以说设计模式是一种思想 ~ 前面说了,设计模式遵循“开放封闭原则”,这里的工厂模式,GirlFriend是用来生成女朋友,它的目的就很纯粹,你给我啥,我就给你 new 啥对象,而FactoryGirlFriend主要是根据出生地,然后给予这个女朋友一些饮食特性。

这里的GirlFriend遵循了“封闭”原则,而FactoryGirlFriend遵循“开放”原则,如果只是使用GirlFriend,需求一旦改变,GirlFriend就会改变。只满足“开放”,不满足“封闭”原则。

抽象工厂

在 《JavaScript 设计模式》书中,对抽象工厂是这么解释的,它的目标是以一个通用的目标将一组独立的工厂进行封装.它将一堆对象的实现细节从它们的一般用例中分离。

现在小何开一个淘宝店铺,专门帮人 new 女朋友,大量生产,于是,小何心想,要 new 一个女朋友,有需要南方姑娘,有需要北方姑娘,这就算了,小戴居然加钱,要我帮他 new 一个东北小姐姐,要那种贼能喝酒的;小刘要我找个西北新疆的漂亮美眉,要超级美的那种 ~

这可咋整,哪天又来一个要东北小姐姐,西北大美人,我就没了...于是呢,他想到了曾经在某本书上看到的抽象工厂

抽象工厂模式创建的是类簇,而非是具体某一个类的实例。抽象工厂模式适用于系统里有多于一个的产品族,而只需要用到某一族的类的场景

// 抽象工厂方法
function AbstractFactoryGirlFriend(child, parent) {
  if (typeof AbstractFactoryGirlFriend[parent] === 'function') {
    function F() {}
    F.prototype = new AbstractFactoryGirlFriend[parent]()
    child.constructor = parent
    parent.prototype = new F()
  } else {
    throw new Error('不能创建该抽象类')
  }
}
// 定义抽象类的结构

// 北方姑娘抽象类
AbstractFactoryGirlFriend.NorthGirl = function() {
  this.position = 'north'
  this.author = '小何'
}
AbstractFactoryGirlFriend.NorthGirl.prototype = {
  northFeature() {
    return new Error('抽象方法不能调用')
  },
  myFeature() {
    return new Error('抽象方法不能调用')
  }
}

// 南方姑娘抽象类
AbstractFactoryGirlFriend.SouthGirl = function() {
  this.position = 'south'
}
AbstractFactoryGirlFriend.SouthGirl.prototype = {
  southFeature() {
    return new Error('抽象方法不能调用')
  },
  myFeature() {
    return new Error('抽象方法不能调用')
  }
}
// 定义具体的类如下
// 北方姑娘: 东北的小姐姐
function NorthEastGirl(name, cup, customize) {
  this.category = '东北'
  this.name = name
  this.cup = cup
  this.customize = customize
}
AbstractFactoryGirlFriend(NorthEastGirl, 'NorthGirl')
NorthEastGirl.prototype.northFeature = function() {
  return `我的名字是${this.name}, 我是个${this.category}姑娘,我的cup是${this.cup}`
}
NorthEastGirl.prototype.myFeature = function() {
  return JSON.stringify(this.customize)
}

// 北方姑娘: 西北的小姐姐
function NorthWestGirl(name, cup, customize) {
  this.category = '西北'
  this.name = name
  this.cup = cup
  this.customize = customize
}
AbstractFactoryGirlFriend(NorthWestGirl, 'SouthGirl')
NorthWestGirl.prototype.northFeature = function() {
  return `我的名字是${this.name}, 我是个${this.category}姑娘,我的cup是${this.cup}`
}
NorthWestGirl.prototype.myFeature = function() {
  return JSON.stringify(this.customize)
}

到了第二天,小谭来找小何...

小谭 : “何少,拜托你的事咋样了?”

小何问 : “再说一遍你的要求”

小谭 : “20 出头,漂亮,36D cup,要东北的,那种啤酒随便灌,白酒五斤半的小姐姐”

小何 : “ojbk~”

const TanNorthEastGirl = new NorthEastGirl('小谭女朋友', '36D', { beer: '随便灌', liquor: '五斤半' })
TanNorthEastGirl.northFeature()
TanNorthEastGirl.myFeature()

小刘一看,马马发红包,别说了,小何: “你别说了,新疆,跪票,你自己去找吧”

const LiuNorthWestGirl = new NorthWestGirl('小刘女朋友', '36C', { sexy: true, beautiful: true})
LiuNorthWestGirl.northFeature()
LiuNorthWestGirl.myFeature()

其他文章

最后多少两句

暂时先讲构造器模式和工厂模式,通过梳理成博客,确实香了很多,对自己理解也有所帮助,下次会输出其他设计模式的文章,不过我还是想说一下,那本小册是真的有用,良心推荐 ~