JS设计模式之工厂模式

本文内容来自于JavaScript 设计模式核心原理与应用实践掘金小册。总结是为了让自己更好的学习。

设计模式的核心思想------封装变化。将变与不变分离,确保变化的部分灵活、不变的部分稳定。其实上研究生的时候看过设计的书,但当时技术水平有限,所以虽然对着书都敲了一遍,但其实应该是没有理解,因为没有足够多的项目经验,其实看了没啥用。过了这么多年重看,虽然有一些会在项目中用到,但用过的还是常见的单例模式、策略模式、观察者模式,其他就很少了。但是设计模式本来是后端总结的东西,应用到前端来其实并不会全部适应。那就掌握常用的,常考的吧,这部分还是有必要掌握的。

SOLID设计原则

首先说到设计模式,要先了解设计模式的原则。这几个原则中,感觉单一功能和开放封闭是最有用的,另外三个其实也没太理解。

"SOLID" 是由罗伯特·C·马丁在 21 世纪早期引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则。

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

简单工厂模式

工厂模式其实就是将创建对象的过程单独封装。和构造函数相关,将创建对象的过程封装起来。看起来挺简单的,有时候用了,可能都不知道。

javascript 复制代码
function User(name, age, career, work) {
    this.name = name
    this.age = age
    this.career = career 
    this.work = work
}

function Factory(name, age, career) {
    let work;
    switch (career) {
        case 'coder':
            work =  ['写代码','写系分', '修Bug'];
            break;
        case 'pm':
            work = ['订会议室', '写PRD', '催更'];
            break;
        case 'boss':
            work = ['喝茶', '看报', '见客户'];
            break;
        case 'xxx':
            break;
        default:
            break;
    }
    return new User(name, age, career, work);
}

抽象工厂模式

这个有点难想起来,没有想到很好的应用场景,给的一个demo是手机制造相关。

抽象工厂模式的定义,是围绕一个超级工厂创建其他工厂。

  1. 先创建一个抽象工厂,作为顶级boss,是创建工厂的工厂,是爸爸,继承这个抽象工厂可以创建不同的工厂,再从具体的工厂new出来新的产品。
javascript 复制代码
// 抽象工厂
class MobilePhoneFactory {
    createOS() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
    createHardWare() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
}

// 具体工厂继承自抽象工厂
class FakeStarFactory extends MobilePhoneFactory {
    createOS() {
        return new AndroidOS();
    }
    createHardWare() {
        return new QualcommHardWare();
    }
}
  1. 具体的工厂有不同的功能,例如对于手机有操作系统,有硬件,而操作系统又有ios、Android,硬件更多,高通等等,对于ios、android是具体的功能,再他们之上呢,又有一层抽象层作为操作系统的抽象层,便于扩展新的操作系统。
scala 复制代码
// 定义操作系统这类产品的抽象产品类
class OS {
    controlHardWare() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
}

// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
    controlHardWare() {
        console.log('android');
    }
}

class AppleOS extends OS {
    controlHardWare() {
        console.log('apple');
    }
}

对于硬件同理,有个抽象层作为爸爸,爸爸不允许操作,儿子继承爸爸实现具体功能,同时可以生出好多不同的儿子。

scala 复制代码
class HardWare {
    operateByOrder() {
        throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    }
}

class QualcommHardWare extends HardWare {
    operateByOrder() {
        console.log('高通1');
    }
}

class QualcommHardWare2 extends HardWare {
    operateByOrder() {
        console.log('高通2');
    }
}

上面一堆类造好了,就来看看具体的创建实例的过程:

ini 复制代码
// 构造具体手机时
const myPhone = new FakeStarFactory();
// 创建操作系统
const myOS = myPhone.createOS();
// 创建硬件
const myHareWare = myPhone.createHardWare();
// 启动操作系统
myOS.controlHardWare();
// 唤醒硬件
myHareWare.operateByOrder();
  1. 创建具体手机
  2. 添加操作系统和硬件,这里可以对上面不同的类做组合
  3. 然后手机可以用了,启动系统。

这个时候,如果FakeStarFactory这个手机过时了,需要生产新的手机产品,则无需修改子类,只需要从最大的boss继承来创建一个新的具体工厂,然后从工厂创建实例即可。就是把内部操作系统和硬件都封闭了起来,留出了扩展的接口。

scala 复制代码
// 如果构造一个新的手机, 
class NewStarFactory extends MobilePhoneFactory {
    createOS() {
        return new AppleOS();
    }
    createHardWare() {
        return new QualcommHardWare2();
    }
}

// 构造具体手机时
const myPhone2 = new NewStarFactory();
// 创建操作系统
const myOS2 = myPhone.createOS();
// 创建硬件
const myHareWare2 = myPhone.createHardWare();
// 启动操作系统
myOS2.controlHardWare();
// 唤醒硬件
myHareWare2.operateByOrder();

看起来就创建示例有区别,其他都一样。

上面总共涉及到了4个角色:

  • 抽象工厂:抽象类,它不能被用于生成具体实例,用于声明最终目标产品的共性。

MobilePhoneFactory

  • 具体工厂:用于生成产品族里的一个具体的产品,继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体的产品的类。

FakeStarFactory、NewStarFactory

  • 抽象产品:抽象类,它不能被用于生成具体实例。上面我们看到,具体工厂里实现的接口,会依赖一些类,这些类对应到各种各样的具体的细粒度产品(比如操作系统、硬件等),这些具体产品类的共性各自抽离,便对应到了各自的抽象产品类。

OS、HardWare

  • 具体产品:用于生成产品族里的一个具体的产品所依赖的更细粒度的产品,比如我们上文中具体的一种操作系统、或具体的一种硬件等。

AndroidOS、AppleOS、QualcommHardWare、QualcommHardWare2

学习完觉得写的挺好,但是没有具体应用的话,不知道会记多久,只有真正在实际项目中应用了,才会成为自己的东西吧,这也是为什么要记录下来,看一遍是最浅的,记录下来可以加深自己的印象。如果应用的话才会真正的掌握,如果把这个讲出来的话就理解的更深了。这就是学习的几个层次。

为了方便大家看,把上面整体代码列一下:

scala 复制代码
// 抽象工厂
class MobilePhoneFactory {
    createOS() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
    createHardWare() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
}

// 具体工厂继承自抽象工厂
class FakeStarFactory extends MobilePhoneFactory {
    createOS() {
        return new AndroidOS();
    }
    createHardWare() {
        return new QualcommHardWare();
    }
}

// 定义操作系统这类产品的抽象产品类
class OS {
    controlHardWare() {
        throw new Error('抽象工厂方法不允许直接调用,你需要将我重写');
    }
}

// 定义具体操作系统的具体产品类
class AndroidOS extends OS {
    controlHardWare() {
        console.log('android');
    }
}

class AppleOS extends OS {
    controlHardWare() {
        console.log('apple');
    }
}

class HardWare {
    operateByOrder() {
        throw new Error('抽象产品方法不允许直接调用,你需要将我重写!');
    }
}

class QualcommHardWare extends HardWare {
    operateByOrder() {
        console.log('高通1');
    }
}

class QualcommHardWare2 extends HardWare {
    operateByOrder() {
        console.log('高通2');
    }
}

class MiWare extends HardWare {
    operateByOrder() {
        console.log('小米');
    }
}

// 构造具体手机时
const myPhone = new FakeStarFactory();
// 创建操作系统
const myOS = myPhone.createOS();
// 创建硬件
const myHareWare = myPhone.createHardWare();
// 启动操作系统
myOS.controlHardWare();
// 唤醒硬件
myHareWare.operateByOrder();

// 如果构造一个新的手机, 
class NewStarFactory extends MobilePhoneFactory {
    createOS() {
        return new AppleOS();
    }
    createHardWare() {
        return new QualcommHardWare2();
    }
}

// 构造具体手机时
const myPhone2 = new NewStarFactory();
// 创建操作系统
const myOS2 = myPhone.createOS();
// 创建硬件
const myHareWare2 = myPhone.createHardWare();
// 启动操作系统
myOS2.controlHardWare();
// 唤醒硬件
myHareWare2.operateByOrder();

小册最后总结的一些话我觉得挺好,摘抄出来给大家分享:

  1. 前端工程师首先是软件工程师。只会写 JavaScript、只理解 JavaScript、只通过 JavaScript 去理解软件世界,是一件可怕的事情,它会窄化你的技术视野------因为 JavaScript 只是编程语言中的一个分支。

这个确实有所感悟,工作这么多年,一直局限在前端,虽然node也会,但是服务端很少写,当然如果有了这么多年软件经验,任何技术花时间都是能学会的,但是并没有去扩展,暂时还并不想去扩展,自己的视野还是不够大。

  1. 抽象工厂是佐证"开放封闭原则"的良好素材,就是SOLID中的O的原则,这个是一直记住的。就记住了两个原则,单一职责和开放封闭,确实是有用的。也会在实际的项目代码中应用上。

  2. 不要小看那些看似"无用"的知识。仅仅因为"这个技术点我现在用不到"而推开摆在眼前的知识,是一种非常糟糕的学习方法------它会极大地限制你的能力和你职业生涯的可能性。

这一条给萌新们看,技术的学习路上,总是在想只学用的,学了看似用不上的东西到底有啥用呢?不知道,当时确实没有,但就像之前看到的一句话:

我们读了很多书,最终依然不可避免地会忘记,但其实它们最终都被吸收到体内,成为了我们的骨血和肌肉。我们享受到了阅读的快感,滋养了灵魂,拥有了自由丰富的思想。
读书对一个人的影响是潜移默化的,时间久了可能读完并不一定会记得看了什么,世界上任何书籍都不能带给你好运,但它们能让你悄悄成为最好的自己。

这句话送给自己,送给大家,一起共勉,愿我们不忘初心,在技术的道路上稳扎稳打。

愿你的指下有代码,眼里有星辰。

本文由mdnice多平台发布

相关推荐
二豆是富婆1 小时前
vue3 element plus table 滚动到指定位置
javascript·vue.js·elementui
学前端搞口饭吃2 小时前
vue2-ssr从vue-cli搭建项目改造服务端渲染+打包上线部署
前端·javascript·vue.js
鱼在在2 小时前
uni-app 聊天界面滚动到消息底部
javascript·uni-app·vue
anyup_前端梦工厂2 小时前
Vue 中常用的基础指令
前端·javascript·vue.js
宝子向前冲4 小时前
React中九大常用Hooks总结
前端·javascript·react.js
F-1257 小时前
关于 vue/cli 脚手架实现项目编译运行的源码解析
前端·javascript·vue.js
web喵神8 小时前
react-pdf预览在线PDF的使用
javascript·web前端·插件·移动端开发
一直在学习的小白~8 小时前
基于React通用的 WebSocket 钩子 useWebSocket
javascript·websocket·react.js
战族狼魂9 小时前
vue axios 如何读取项目下的json文件
javascript·vue.js·json
星河路漫漫9 小时前
JavaScript高阶面试题:(第三天)
java·javascript·面试