关于设计模式, 不论是静态类型语言,仍是像JavaScript这样的动态类型语言,都常常出现,这些设计模式,万变不离其宗,重要的是理念,而不是概念,全部的模式都是前人的总结,重点不是怎么去把这些模式硬塞到项目中,而是学会如何在恰当的位置,以事半功倍的方式去使用他们。javascript
设计模式基于的原则SOLID:vue
1. 单一功能原则-Single Responsibility Principlejava
2. 开放封闭原则 - Opened Closed Principlevuex
3. 里式替换原则 - Liskov Substitution Principleredux
4. 接口隔离原则 - Interface Segregation Principle设计模式
5. 依赖反转原则 - Dependency Inversion Principle闭包
看完并非这些都是什么概念。。。嗯,接着往下学。app
设计模式的核心是-封装变化。iphone
意思是: 将变与不变隔离开,不变的稳定下来,变化的部分封装起来,确保迭代的时候改动最小。函数
23种设计模式,看的眼花,基本记不住。
建立型、结构型、行为型。大体分为这三种。
function Person(name, age, gender) {
this.name = name
this.age = age
this.gender = gender
}复制代码
上面的例子是构造函数,调用它的方式:
const person1 = new Person('张三', 24, '男')复制代码
构造器模式就是用构造函数Person来初始化对象person1。
能够发现,变的是person的属性值,不变的是这些共性的属性。
构造器模式是抽象了对象实例的变与不变。
工厂模式: 抽象不一样构造函数之间的变与不变。
上面的例子里,稳定的是person的属性,可是若是要添加新的属性,给不一样的性别描述,就要往构造函数中手动添加,那么这个构造函数也变的相对不稳定了。
这时候,要把这个变也抽出来。
function Man(name, age) {
this.name = name
this.age = age
this.gender = '男'
this.genderDesc = '孔武有力'
}
function Woman(name, age) {
this.name = name
this.age = age
this.gender = '女'
this.genderDesc = '温柔似水'
}
function factory(name, age, gender) {
switch (gender) {
case '男':
return new Man(name, age)
break
case '女':
return new Woman(name, age)
break
}
}复制代码
这只是性别的描述,只判断了两个,若是是别的,职业、爱好...要添加在factory中的判断就不少不少。
在上面的例子中,无论男女,都有name、age、gender、genderDesc, 值都是变的,genderDesc随着gender的变化而变化。
function Person(name, age, gender, genderDesc) {
this.name = name
this.age = age
this.gender = gender
this.genderDesc = genderDesc
}
function factory(name, age, gender) {
let genderDesc
switch (gender) {
case '男':
genderDesc = '孔武有力'
break
case '女':
genderDesc = '温柔如水'
break
}
return new Person(name, age, gender, genderDesc)
}复制代码
这样写好像好多了。
这就是工厂模式:
将建立对象的过程单独封装,目的就是为了无脑传参。
构造器是解决了不断实例的问题,工厂模式解决了多个类的问题。
什么叫抽象类,java这种强类型的静态语言中,会在编译阶段就对类型进行检查,定义的是什么类型,若是值不对,编译的时候直接就抛出异常了,建立对象时,要关注类型之间的解耦,来保证多态性,然而在JavaScript中,自然就具备多态性,看起来好像不须要抽象类。
上面那个例子,若是是对不一样的工做内容进行抽取判断,不一样的工做岗位,权限也是不同的,好比管理岗,外包,等,每次新加一个职业群体,就得在factory中加一个判断,会越加越多,维护起来也很复杂。
开放封闭原则: 对拓展开放,对修改封闭。意思是,实体(类、模块、函数)能够扩展,可是不能够修改。
不停的添加判断就是在修改,不是在扩展。
下面一个例子:
手机最重要的是操做系统和硬件两部分,手机厂商量产手机时能够建立以下类:
class MobileFactory {
// 操做系统接口
createOS(){
throw new Error("抽象工厂方法不容许直接调用,你须要将我重写!")
}
// 硬件接口
createHardWare(){
throw new Error('抽象工厂方法不容许直接调用, 你须要将我重写')
}
}复制代码
上面的类除了声明量产手机的通用线, 定下规矩 ,啥也不干,不能够直接调用。在抽象工厂里,这个类就是食物链顶端的抽象工厂AbstractFactory。
抽象工厂不干活,干活是具体工厂的事情(ConcreteFactory)。当明确一条线要生产什么手机,具体工厂的工做就明确了,若是如今要生产一个Android+高通硬件的手机,名为FakeStar,就能够为FakeStar建立一个具体工厂:
class FakeStarFactory extends MobileFactory{
createOS(){
// 安卓系统实例
return new AndroidOS()
}
createHardWare(){
// 高通硬件实例
return new QualcommHardWare()
}
}复制代码
此处咱们调用了两个构造函数AndroidOS和QualcommHardWare。像这样的用来new出具体对象的类,叫具体产品类-ConcreteProduct。这种类每每不会孤立存在,不一样具体产品每每会有相同的功能,安卓和iOS都有操做系统和硬件系统,所以咱们能够创造一个抽象产品类-AbstractProduct,来声明这一类产品应该具备的基本功能。
// 定义操做系统这类产品的抽象产品类
class OS {
controlHardWare(){
throw new Error('我不容许被直接调用')
}
}
// 定义具体操做系统的具体实现类
class AndroidOS extends OS {
controlHardWare() {
console.log('我会用安卓的方式操做硬件')
}
}
class AppleOS extends OS {
controlHardWare(){
console.log('我会用苹果的方式操做硬件')
}
}复制代码
硬件类产品同理
// 定义硬件这类产品的抽象产品类
class HardWare {
// 手机硬件产品的共性方法,这里是根据命令运转
operateByOder() {
thew new Error('我不容许被直接调用')
}
}
// 定义具体硬件的具体实现类
class QualcommHardWare extends Hardware {
operateByOrder(){
console.log('用高通方式运转')
}
}
class MiHardWare extends HardWare {
opreateByOrder(){
console.log('用小米方式运转')
}
}复制代码
这样一来,咱们生产一部FakeStar,只须要这样
// 手机
const iphone6 = new FakeStarFactory()
// 手机的操做系统
const myOS = iphone.createOS()
// 手机的硬件
const myHardWare = iphone.createHardWare()
// 启动操做系统
myOS.controlHardWare()
// 唤醒硬件
myHardWare.operateByOrder()复制代码
FakeStar过气了,再生产别的手机,咱们也不须要对MobileFactory作任何修改,再新建一个手机种类就好了。
class FakeProFactory extends MobileFactory {
createOS(){
return new ...
}
createHardWard(){
return new ...
}
}复制代码
这样他不会对现有的模块形成任何影响。
他们都将同与不一样分离,场景复杂度不一样而已。
简单工厂模式里,处理的对象是类,逻辑自己比较简单,不苛求可扩展性。
抽象工厂模式里,处理对象也是类,可是类比较复杂,类中分出了品种和等级,存在着不少可能性,须要是可悲扩展的。这时候有了四种角色:
抽象工厂-是一个抽象类,不能被用于生成具体实例-他用来声明不一样产品的共性,一个系统里可能有多个抽象工厂,好比手机、平板、手提电脑等。他们统称为产品族。
具体工厂- 用来生产产品族中的某一个产品-继承自抽象工厂,实现了抽象工厂中声明的那些方法,用于建立具体产品的类。
抽象产品-抽象类,不能用于生成具体实例-上面例子中,具体产品中实现的接口会依赖一些类,这些具体产品类的共性各自抽离,对应到各自抽象产品类。
具体产品-用于生成产品族中更细粒度的产品- 好比上面的某一种操做系统,某一种硬件等。
抽象工厂就是围绕一个超级工厂来建立其余厂,JavaScript中用的不太多,上面的例子算是用ES6的方式来模拟Java的抽象类。
单例模式的核心: 整个程序有且只有一个实例。该类负责建立本身的对象,同时确保只有一个对象被建立。
单例模式比较简单,在Vuex中被应用。
有且只有一个实例,这句话让人有点困惑,通常状况下咱们建立了一个类以后,能够用new来实例化,想实例化几个就实例化几个,怎么来确保只有一个实例呢??
也就是说单例模式想实现的是无论怎么实例化,都要返回的是最初建立的那个实例。这须要该类(实质上是构造函数)有能力判断本身是否已经实例化过。
class SingDog {
show(){
console.log('我是一个实例对象')
}
// 把判断实例的逻辑放入静态方法中,其实也能够直接写入构造函数的函数体内
static getInstance(){
// 判断是否已经new过一个实例
if(!SingDog.instance) {
// 若是实例不存在,先建立他
SingDog.instance = new SingDog()
} else {
// 若是已经存在实例 就直接返回
return SingDog.instance
}
}
}
const instance1 = SingDog.getInstance()
const instance2 = SingDog.getInstance()
instance1 === instance2 // true复制代码
在ES6中static关键字:
定义一个类成员,使他彻底独立于该类的任何对象
它能被类自己使用,而没必要引用任何特定的实例。
看完定义,仍是有点抽象。
类是实例的原型,以前new 一个test(),那么test中全部的方法都会被实例继承,但若是在一个方法前加上static,表示该方法不会被实例继承,而是直接经过类来调用。
父类中的静态方法能够被子类继承。
或者用闭包实现getInstance
SingDog.getInstance = (function(){
// 定义自由变量instance, 模拟私有变量
let instance = null
return function(){
// 判断instance 是否存在
if(!instance) {
instance = new SingDog()
} else {
return instance
}
}
})复制代码
在getInstance拦截下,无论调多少次都只有一个实例对象。
状态管理不论是redux仍是vuex,都实现全局Store存储应用的全部状态。
Vuex中使用了单一状态树,就是一个对象存储了全部状态,他是惟一一个数据源。由于用了单例模式,意味着整个应用只有一个store实例。
Vue中组件相互独立,想要通讯,通常用props,但props只用在父子之间,复杂的好比兄弟组件通讯,就用不了props了。
这时咱们能够把数据源放在全局,数据源一变,你们都变。
Vuex中保证store惟一性的办法:
Vue.use(Vuex)
new Vue({
el: '#app',
store
})复制代码
经过调用Vue.use()安装了一个Vuex插件,Vuex是一个对象,他内部实现了一个install方法,此方法在插件安装时被调用,将store注入到Vue实例中。每安装一次,都尝试实例一个新的store。
因此在install中就有一段判断来拦截屡次实例化。