前言
设计模式属于前端的高级内容,建议开始学习之前先确保已经可以熟练使用 JS,无论是ES5、ES6
都能较好地运用,同时还要了解面向对象
编程的概念,如果还学过TS
那就最好了,不过没学过也没关系,因为其语法和 JS 大同小异,如果熟悉 JS 那么上手 TS 就不会那么难了。
为什么要用设计模式
在开发逻辑较为复杂的项目时,学会设计模式会使得代码更加清晰,同时还会增加开发效率。不过说白了设计模式也不是什么“只可远观而不可亵玩焉”的东西,只要抱着一定要学会的决心,相信学会设计模式也只不过是时间问题。
什么是工厂模式
简单来说,工厂模式的目的就是让你根据需要让工厂生产产品给你使用;
使用工厂模式的方法可以归结为以下两点:
- 定义产品类型 (世界上有什么)
- 指定工厂给你生产你想要的产品
在工厂模式中有几个术语这里提前介绍一下,一下子不明白的不用立马理解,在下面分析代码的时候会讲到
- 抽象产品类 ==> 规定产品有什么功能
- 具体产品类 ==> 一个产品的模型 (其
实例
就是我们要的产品) - 抽象工厂类 ==> 规定一家工厂有什么功能 (可以生产什么产品)
- 具体工厂类 ==> 一家工厂,通过调用暴露的方法来生产产品
1.简单工厂模式
在该模式下,我们会先定义产品,然后将所有的产品都交给一个工厂生产;
我们举个计算器的例子:
- 产品:加减乘除等运算功能
- 工厂:创造计算器实例的类
TS
// 计算模式 抽象产品类
interface Calculate {
getResult(x:number, y:number):number;
}
// 具体产品类
class AddFn implements Calculate{
getResult(x: number, y: number): number {
return x + y;
}
}
class SubFn implements Calculate{
getResult(x: number, y: number): number {
return x - y;
}
}
class MulFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x * y).toFixed(3) * 1000 / 1000;
}
}
class DivFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x / y).toFixed(3) * 1000 / 1000;
}
}
class RemFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x % y).toFixed(3) * 1000 / 1000;
}
}
// 计算器工厂
class CalculatorFactory {
static buildCalculator(type:string):any{
switch (type) {
case 'add':
return new AddFn();
case 'sub':
return new SubFn();
case 'mul':
return new MulFn();
case 'div':
return new DivFn();
case 'rem':
return new RemFn();
default:
console.log('算了个寂寞');
}
}
}
const add = CalculatorFactory.buildCalculator('add');
const sub = CalculatorFactory.buildCalculator('sub');
const mul = CalculatorFactory.buildCalculator('mul');
const div = CalculatorFactory.buildCalculator('div');
const rem = CalculatorFactory.buildCalculator('rem');
console.log(add.getResult(1, 3.5));
console.log(sub.getResult(1, 3));
console.log(mul.getResult(1.2, 3));
console.log(div.getResult(6, 1.2));
console.log(rem.getResult(6, 4));
分析:
interface
用来定义一个接口,implements
用来表示这个类要使用某些接口,使用了接口的类必须要使用接口定义了的方法,当然也能额外定义自己的专有方法;
根据上述接口特性,我们将同一种产品的功能都定义在接口中,这个接口就是抽象产品类
;
可以这么来理解,汽车肯定有前进、后退、开车头灯等功能,这些功能就可以定义在接口中,而具体的产品又可以有自己的特点,比如这辆车可以是宝马、奔驰、奥迪等等,而使用了接口的类,我们又叫它具体产品类
(一张告诉你怎么实例化的蓝图);- 本例的“计算器工厂”在理论上又称为
具体工厂类
,因为这个工厂类通过调用其方法就可以返回一个产品实例(在本例中是加减乘除取余等运算方法),这正是我们想要的;
当然这里也能用 JS
来表达相同的意思
JS
// 简单工厂模式
// 计算模式 抽象产品类
class Calculate {
getResult(x, y){ }
}
// 具体产品类
class AddFn extends Calculate{
getResult(x, y) {
return x + y;
}
}
class SubFn extends Calculate{
getResult(x, y) {
return x - y;
}
}
class MulFn extends Calculate{
getResult(x, y) {
return (x * y).toFixed(3) * 1000 / 1000;
}
}
class DivFn extends Calculate{
getResult(x, y) {
return (x / y).toFixed(3) * 1000 / 1000;
}
}
class RemFn extends Calculate{
getResult(x, y) {
return (x % y).toFixed(3) * 1000 / 1000;
}
}
// 计算器工厂
class CalculatorFactory {
static buildCalculator(type){
switch (type) {
case 'add':
return new AddFn();
case 'sub':
return new SubFn();
case 'mul':
return new MulFn();
case 'div':
return new DivFn();
case 'rem':
return new RemFn();
default:
console.log('算了个寂寞');
}
}
}
const add = CalculatorFactory.buildCalculator('add');
const sub = CalculatorFactory.buildCalculator('sub');
const mul = CalculatorFactory.buildCalculator('mul');
const div = CalculatorFactory.buildCalculator('div');
const rem = CalculatorFactory.buildCalculator('rem');
console.log(add.getResult(1, 3.5));
console.log(sub.getResult(1, 3));
console.log(mul.getResult(1.2, 3));
console.log(div.getResult(6, 1.2));
console.log(rem.getResult(6, 4));
备注:
因为语言本身的特点,我觉得没必要在每种语言上都严格按照一个设计模式的初衷,就比如在 JS
中我们就可以用 伪多态
来实现接口这个功能。有的人可能就要较真这怎么就是接口了呢,明明在子类中定不定义接口里的方法都没关系。然而在 JS
中最多也只能做到这样了,有的东西意思到了即可,太较真的话我觉得 duck可不必~~
简单工厂模式的缺点
从上面代码可以看到每个产品的生产都交给一个工厂来解决,当产品数量较多、实现逻辑比较复杂的时候这个工厂就会承受很大的压力,我们看起来也不好维护,而且“迁一发则动全身”,如果有人在增加产品的时候不小心写多了一个分号或者做了其他造成语法错误的行为,那么整个工厂就瘫痪掉了。。
为了解决这个问题,有人提出了一个工厂方法。。
2.工厂方法
工厂方法有很多地方和上文介绍过的简单工厂模式相似,最大的不同点是在该模式下,不同产品交给不同工厂解决,也就是说专一性更强了,下面来看代码
TS
// 工厂模式
// 定义产品功能接口 抽象产品类
interface Calculate {
getResult(x:number, y:number):number;
}
// 具体产品类
class AddFn implements Calculate{
getResult(x: number, y: number): number {
return x + y;
}
}
class SubFn implements Calculate{
getResult(x: number, y: number): number {
return x - y;
}
}
class MulFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x * y).toFixed(3) * 1000 / 1000;
}
}
class DivFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x / y).toFixed(3) * 1000 / 1000;
}
}
class RemFn implements Calculate{
getResult(x: number, y: number): number {
// @ts-ignore
return (x % y).toFixed(3) * 1000 / 1000;
}
}
// 定义计算器工厂功能 抽象工厂类
interface CalculatorFactory {
buildCalculator():any;
}
// 具体工厂类
class AddFactory implements CalculatorFactory{
buildCalculator(): AddFn {
return new AddFn();
}
}
class SubFactory implements CalculatorFactory{
buildCalculator(): SubFn {
return new SubFn();
}
}
class MulFactory implements CalculatorFactory{
buildCalculator(): MulFn {
return new MulFn();
}
}
class DivFactory implements CalculatorFactory{
buildCalculator(): DivFn {
return new DivFn();
}
}
class RemFactory implements CalculatorFactory{
buildCalculator(): RemFn {
return new RemFn();
}
}
// 工厂实例
const add = new AddFactory().buildCalculator();
const sub = new SubFactory().buildCalculator();
const mul = new MulFactory().buildCalculator();
const div = new DivFactory().buildCalculator();
const rem = new RemFactory().buildCalculator();
console.log(add.getResult(1, 3.5));
console.log(sub.getResult(1, 3));
console.log(mul.getResult(1.2, 3));
console.log(div.getResult(6, 1.2));
console.log(rem.getResult(6, 4));
分析:
- 相信眼见的小伙伴发现了这里多了一个接口,这个新的接口就是
抽象工厂类
,和抽象产品类
相似,这个抽象工厂类
是用来定义一家工厂能生产什么产品的 - 根据代码我们可以看到加减乘除等功能被安排在不同的工厂进行实例,这样当我们需要一个产品的时候只需要找相应的工厂进行实例化即可
- 这样做有一个很大的有点就是,当我们需要增加产品的时候,只需要在原来的基础上新增就行,这样做就算新增的工厂有问题,也完全不会影响到原来的工厂
- 本例的
JS
实现和案例一差不多,这里就不再赘述了
到这里,我们又可以发现一个问题——在该模式下,每个工厂只能生产一件产品,这样当产品多的时候工厂的数量就会多得难以管理;
就比如说,一家造汽车的工厂不可能一间厂就造一种车,肯定是好几种同时进行的,为此,有人提出了抽象工厂模式
这个概念。。。
3.抽象工厂模式
这个模式有很多地方和工厂方法类似,最大的不同点在于这个模式下,一家工厂可以生产多个产品,这次我们换一个例子
就拿百度定图和高德地图来说,不管是哪一家的地图,最起码的定位和视图渲染功能是必须的
TS
// 抽象工厂模式
// 定义产品功能 抽象产品类
// 定义定位功能
interface Locate {
locate():any;
}
// 定义视图渲染功能
interface RenderView {
renderView():any;
}
// 定义具体产品(虚拟产品)
// 定位功能产品
class GaodeLocate implements Locate{
locate(): any {
console.log('高德地图获得目标位置');
}
}
class BaiduLocate implements Locate{
locate(): any {
console.log('百度地图获得目标位置');
}
}
// 视图渲染功能产品
class GaodeRenderView implements RenderView{
renderView(): any {
console.log('高德地图渲染视图');
}
}
class BaiduRenderView implements RenderView{
renderView(): any {
console.log('百度地图渲染视图');
}
}
// 定义工厂功能 抽象工厂类
interface FactoryMode {
locateFn():any;
renderFn():any;
}
// 具体工厂实现 具体工厂类
class GaodeFactory implements FactoryMode{
locateFn(): GaodeLocate {
return new GaodeLocate();
}
renderFn(): GaodeRenderView {
return new GaodeRenderView();
}
}
class BaiduFactory implements FactoryMode{
locateFn(): BaiduLocate{
return new BaiduLocate();
}
renderFn(): BaiduRenderView{
return new BaiduRenderView();
}
}
// 工厂实例
// 百度工厂实例
let baidu = new BaiduFactory();
baidu.locateFn().locate();
baidu.renderFn().renderView();
// 高德工厂实例
let gaode = new GaodeFactory();
gaode.locateFn().locate();
gaode.renderFn().renderView();
分析:
- 这里和
工厂方法
不一样的点其实就是定义工厂的类不止有一个方法,每家工厂可以生产不止一个产品
总结:
- 不管是哪种模式,首先产品要定义好,这是必不可少的;其次就是要定义好工厂
- 三种模式没有说哪个好哪个坏,具体还是要看实现场景,如果业务场景比较简单那么用第一种和第二种即可,但如果考虑到可持续发展,那么就要用第三种了
- 在这个设计模式中,“抽象”能力是很必要的;对于同一类产品,不管最终实例有什么不同,我们把同一类产品的共同点抽象出来即可;
比如说:加减乘除,它们的共同点都是输入数字、输出数字,这样我们只需要定义一个接口即可,而不用定义加减乘除四个接口
最后,再啰嗦一下,对于刚刚接触设计模式的同学,不管刚刚有没有看懂上文,都要手动实现一下,最好实现个三遍五遍,特别是看得一头雾水的同学。。我相信只要你跟着敲它个好几遍,自己也能领悟的,边敲还要边思考这部分代码是干什么的,这样才有进步;能看懂的同学也不要太骄傲了,我相信有很多人是脑子看懂了但是你的手还没懂,跟着敲几遍案例是有点麻烦、是有点累,但是这是确实有效的!!
如果文中有什么瑕疵欢迎评论区留言或私信我,大家加油!!