设计模式
提示
掘金小册《JavaScript 设计模式核心原理与应用实践》 阅读笔记
前端工程师的成长论
未来的关键在于培养扎实的基本功、过硬的业务素质和灵活的适应能力,而不是过分关注行业的发展方向
在竞争激烈的环境中,需要思考自己的核心竞争力是什么,以突出自己的特点
一个前端工程师的本质并非取决于瞬息万变的技术点,而是三个层次的能力:使用健壮的代码解决具体问题、应对复杂系统的抽象思维能力,以及规划大规模业务的工程化思维。后两种能力可以说是对架构师的要求。事实上,能做到第一点并且把它做到扎实、做到娴熟的人,已经堪称同辈楷模
基础理论知识是一个人的基线,理论越强,基线越高。设定目标并不断向上攀升,达到目标只是时间问题。相比之下,许多野路子工程师即使经过多年努力也无法达到优秀工程师的基线,他们所追求的高深学问对于正规工程师而言是很自然的东西
架构是许多人渴望追求的高大上话题,然而,很多人缺乏的并非高瞻远瞩的激情,而是“不变能力”中最基本的一点,即用健壮的代码解决具体问题的能力。在软件工程领域中,设计模式正是对应于这种能力的经典知识体系
要成为靠谱的开发者,首先需要掌握设计模式
设计模式的”道“与”术“
模式的含义:模式描述了我们周围不断重复发生的问题以及解决方案的核心。通过使用模式,我们能够反复应用该方案,避免重复劳动
在实际开发中,代码很少是不变的,我们需要处理变化。封装变化的目标是将变化的影响最小化,将可变部分与不变部分分离开来。这样做可以确保变化的部分具有灵活性,而不变的部分保持稳定。通过这种方式,我们可以编写出健壮的代码,即能够经受住变化考验的代码。而设计模式出现的意义,就是帮我们写出这样的代码
创建型
工厂模式·简单工厂
区分变与不变
使用构造函数去初始化对象,就是应用了构造器模式:
function User(name, age, career) {
this.name = name
this.age = age
this.career = career
}
创建一个 user
过程中,变的是每个 user
的姓名、年龄、工种,这是用户的个性,不变的是每个员工都具备姓名、年龄、工种这些属性,这是用户的共性
用构造器模式的时候,我们本质上是去抽象了每个对象实例的变与不变。那么使用工厂模式时,我们要做的就是去抽象不同构造函数(类)之间的变与不变
现在要给每个工种的用户加上一个个性化的字段,来描述他们的工作内容
Coder
和 ProductManager
两个工种的员工,是不是仍然存在都拥有 name
、age
、career
、work
这四个属性这样的共性?它们之间的区别,在于每个字段取值的不同,以及 work
字段需要随 career
字段取值的不同而改变
相同的逻辑封装回 User
类里,然后把这个承载了共性的 User
类和个性化的逻辑判断写入同一个函数,我们只需要像以前一样无脑传参
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 'product manager':
work = ['订会议室', '写PRD', '催更']
break
case 'boss':
work = ['喝茶', '看报', '见客户']
case 'xxx':
// 其它工种的职责分配
...
return new User(name, age, career, work)
}
}
工厂模式的简单之处,在于它的概念相对好理解:将创建对象的过程单独封装,这样的操作就是工厂模式。同时它的应用场景也非常容易识别:有构造函数的地方,我们就应该想到简单工厂;在写了大量构造函数、调用了大量的 new、自觉非常不爽的情况下,我们就应该思考是不是可以掏出工厂模式重构我们的代码了
工厂模式·抽象工厂
理解”开放封闭“
开放封闭:对拓展开放,对修改封闭。软件实体(类、模块、函数)可以扩展,但是不可修改
JavaScript
中没有抽象类的概念,所以本节示例使用TypeScript
编写
// 手机抽象类 最上级工厂
abstract class MobilePhoneFactory {
// 提供操作系统的接口
abstract createOS(): void
// 提供硬件的接口
abstract createHardWare(): void
}
// 系统抽象类
abstract class OS {
abstract controlHardWare(): void
}
// 硬件抽象类
abstract class HardWare {
abstract operateByOrder(): void
}
// 具体工厂
class IphoneFactory extends MobilePhoneFactory {
createOS() {
// 提供苹果系统实例
return new Ios()
}
createHardWare() {
// 提供苹果硬件实例
return new AppleHardWare()
}
}
//ios
class Ios extends OS {
controlHardWare() {
console.log('苹果系统操控硬件')
}
}
// 苹果硬件
class AppleHardWare extends HardWare {
operateByOrder() {
console.log('苹果硬件运转')
}
}
// 这是我的手机
const myPhone = new IphoneFactory()
// 让它拥有操作系统
const myOS = myPhone.createOS()
// 让它拥有硬件
const myHardWare = myPhone.createHardWare()
// 启动操作系统
myOS.controlHardWare()
// 唤醒硬件
myHardWare.operateByOrder()
如果我们想生产安卓手机,不需要对抽象工厂 MobilePhoneFactory 做任何修改,只需要拓展它的种类
对原有的系统不会造成任何潜在影响 所谓的“对拓展开放,对修改封闭”就这么圆满实现了。之所以要实现抽象产品类(OS 类,硬件类)是一样的道理
抽象工厂和简单工厂的异同:
共同点:都是分离一个系统中变与不变的部分
不同点:不同点在于场景的复杂度。在简单工厂的使用场景里,处理的对象是类,它们的共性容易抽离且逻辑比较简单,故而不苛求代码可扩展性。抽象工厂本质上处理的其实也是类,但是是一帮复杂的类,它们存在着千变万化的扩展可能性——这使得我们必须对共性作更特别的处理、使用抽象类去降低扩展的成本,同时需要对类的性质作划分,于是有了这样的四个关键角色:
- 抽象工厂(抽象类,它不能被用于生成具体实例): 用于声明最终目标产品的共性。在一个系统里,抽象工厂可以有多个(大家可以想象我们的手机厂后来被一个更大的厂收购了,这个厂里除了手机抽象类,还有平板、游戏机抽象类等等),每一个抽象工厂对应的这一类的产品,被称为“产品族”。
- 具体工厂(用于生成产品族里的一个具体的产品): 继承自抽象工厂、实现了抽象工厂里声明的那些方法,用于创建具体的产品的类。
- 抽象产品(抽象类,它不能被用于生成具体实例): 上面我们看到,具体工厂里实现的接口,会依赖一些类,这些类对应到各种各样的具体的细粒度产品(比如操作系统、硬件等),这些具体产品类的共性各自抽离,便对应到了各自的抽象产品类。
- 具体产品(用于生成产品族里的一个具体的产品所依赖的更细粒度的产品): 比如我们上文中具体的一种操作系统、或具体的一种硬件等。
抽象工厂模式是围绕一个超级工厂创建其他工厂
单例模式
vuex 的数据管理哲学