设计模式
设计模式
先说明一下什么是工厂模式
- 把创建对象的过程进行级简单封装。
- 目的就是无脑传参
1、创建型之工厂模式
- 构造器模式
- 不再手写字面量 变得是个性,不变的是共性,构造器就是将将共性保存起来不变,开发个性灵活使用
- 简单工厂
- 我们先来看下面两段构造器代码,我们假设公司有多种不同的工种,我们需要录入name,age,career,work,等四个不同的参数
上面这种写法可行,但是我们总不能一直人工判断工种去分配构造器,所以我们可以把中间的变交给函数处理//我们先用不同构造器构造不同的工种 function Coder(name,age){ this.name = name this.age = age this.career = "Coder" this.work = ["写代码","改bug","掉头发"] } function Boss(name,age){ this.name = name, this.age = age, this.career = "Boss", this.work = ["管理","陪客户"] }
通过函数的方法确实可以做到自动分配,但是如果工种多了的话怎么办呢?但是如果每一个工种都写过类去区分,未免有点太蠢了?我们之前说构造器的时候提到过两个东西:个性和共性。我们继续规划我们的构造器function Factory(name,age,career){ switch(career){ case "coder": return new Coder(name,age) break; case "Boss": return new Boss(name,age) break; ... } }
通过这样的处理方式,我们只需要一种构造器就可以把多种工种分配清楚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 = ["管理","陪客户"] break; ... } return new User(name,age,career,work) }
- 抽象工厂
- 上面写的简单工厂例子看上去没有很大问题,但是禁不起改造,如果这时候我们想要对每个工种增加权限怎么办?赋予不同的职能怎么办?这就可能需要对Factory大肆修改,这样的操作会导致函数体变得非常庞大不好维护,为什么会出现这种问题?—— 没有遵守开放封闭原则。(对拓展开放,对修改封闭)
- 举个例子(生产手机)
假如生产一部手机需要两个部分:操作系统(OS)和硬件(HardWare),现在我要开一家山寨手机工厂,那么我这家工厂就需要准备以上两种东西才能实现量产,但是OS和hardWare背后又不同品牌的厂商,所以我不知道我一条具体的生产线需要生产怎么样的一种手机。已知条件只有手机组成——— 我可以先创建一个抽象类约定手机的组成。
上面的类干了啥?啥也没干,只是约定了手机流水线的能力。如果你需要它去干点啥,new一个实例还给你抛出错误!夺笋呐!!所以这个类算是始祖,处在食物链顶端的,诶!我就是啥也不干,我就是定个规矩在这。这就是一个抽象工厂。class MobilePhoneFactory{ OS(){ throw new Error("抽象工厂方法不允许直接调用,你需要将我重写") } HardWare(){ throw new Error("抽象工厂方法不允许直接调用,你需要将我重写") } }
抽象工厂不干活,那么具体工厂就需要来干活。我们已经确定了方案,明确一个流水线生产什么样的手机之后,就可以化抽象为具体。再举个栗子。
我要生产系统A+硬件A手机,可以确定一个具体工厂
这边调用了两个构造函数:OSA和HardWareA,分别生成系统和硬件。拿来用于 new 出具体对象的类,叫具体产品类(ConcreteProduct)class AMobilePhoneFactory{ OS(){ return new OSA() } HardWare(){ return new HardWareA() } }
具体产品类一般都有共同的功能,都有着最基本的组成,因此我们可以用一个抽象产品类(AbstractProduct)来声明这一类产品应该具有的基本功能。
操作系统类
硬件系统类//定义抽象产品类 class OS{ controlHardWare(){ throw new Error("抽象产品方法不允许直接调用,你需要将我重写!") } } //定义具体产品类 class AndroidOS extends OS{ controlHardWare(){ console.log("安卓系统操作硬件方法") } } class AppleOS extends OS{ controlHardWare(){ console.log("苹果系统操作硬件方法") } }
那么现在我们需要生产一台手机,只需要如下操作:// 定义手机硬件这类产品的抽象产品类 class HardWare { // 手机硬件的共性方法,这里提取了“根据命令运转”这个共性 operateByOrder() { throw new Error('抽象产品方法不允许直接调用,你需要将我重写!'); } } // 定义具体硬件的具体产品类 class QualcommHardWare extends HardWare { operateByOrder() { console.log('我会用高通的方式去运转') } } class MiWare extends HardWare { operateByOrder() { console.log('我会用小米的方式去运转') } }
这样做的好处:如果有一天我们这样的手机了,想换个新款,全新的软件和硬件,那我们不需要对抽象工厂做任何修改,拓展新的类就可以了。//先实现一个具体工厂 class myMobilePhone extends MobilePhoneFactory{ OS(){ //安卓系统 return new AndroidOS() } HardWare(){ //小米硬件 return new MiWare() } } //实例一台手机 const myPhone = new myMobilePhone() //先拥有操作系统 const myOS = myPhone.OS() //再拥有硬件 const myHardWare = myPhone.HardWare() //运行结果 myOS.controlHardWare(); //安卓系统操作硬件方法 myHardWare.operateByOrder(); //我会用小米的方式去运转
对原有的系统没有任何潜在影响这就是之前提到的开放封闭原则。
2、创建型之单例模式(Vuex的实现模式)
-
保证一个类只有一个实例,并提供一个访问它的全局访问点,叫做单例模式。
-
什么东西?一个类一个实例?酱紫写?一般情况下,我们一个类都可以创建任意多的实例:
function SingleDog(){ this.show =function(){ console.log("我是一单例对象") } } const dog1 = new SingleDog() const dog2 = new SingleDog() dog1.show() console.log(dog1 === dog2) //false
-
如何实现是个问题哈,我们创建多少次他都只返回第一次创建的示例才行,不然永远没法相等
function SingleDog() { this.show = function () { console.log("我是一单例对象") } this.getInstance = function () { // 判断是否已经new过1个实例 if (!SingleDog.instance) { // 若这个唯一的实例不存在,那么先创建它 SingleDog.instance = new SingleDog() } // 如果这个唯一的实例已经存在,则直接返回 return SingleDog.instance } } const dog1 = new SingleDog().getInstance() const dog2 = new SingleDog().getInstance() console.log(dog1 === dog2) //false
3、创建型之原型模式
- 原型范式:利用实例来描述对象,用实例作为定义对象和继承的基础。体现:基于原型链的继承。
- 原型
- 每个构造函数都有一个prototype属性,指向构造函数的原型对象,这个原型对象中有一个constructor属性指回构造函数
- 每个实例都有一个__proto__属性,构造函数创建实例时,实例的__proto__属性就会指向构造函数的原型对象。
function cuteCat(name,age){
this.name = name;
this.age = age;
};
cuteCat.prototype.eat = function(){
console.log("可爱小猫吃东西");
};
const aCat = new cuteCat('小猫1',2)
console.log(aCat.__proto__)
aCat.eat() //可爱小猫吃东西
aCat.toString() //"[object Object]"
整个过程会形成一个链式调用过程,最终组成原型链,几乎所有JS对象原型链顶端是一个Object,
除了Object.prototype或者Object.create(null)创建一个没有任何原型的对象,那它也不是Object实例
4、行为型之策略模式(非常有用)
举个栗子: 一个商品价格处理多个逻辑情况
- 当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
- 当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
- 当价格类型为“返场价”时,满 200 - 50,不叠加
- 当价格类型为“尝鲜价”时,直接打 5 折
我擦!老子直接if-else一把梭哈!
function askPrice(tag,originPrice){
//预售价
if(tag==='pre'){
if(originPrice >= 100){
return originPrice-20
}
return originPrice*0.9
}
//大促价
if(tag==='onSale'){
if(originPrice >= 100){
return originPrice-30
}
return originPrice*0.8
}
//返厂价
if(tag === 'back'){
if(originPrice >= 200){
return originPrice - 50
}
return originPrice
}
//尝鲜价
if(tag ==='fresh'){
return originPrice*0.5
}
}
领导:一个函数体,你他妈的写4个逻辑,严重违背单一原则!快似了得了!还违背开放封闭原则,怎么添加新逻辑,继续if-else?
重构方法
先走单一原则改造
//预热价
function pre(originPrice){
if(originPrice >= 100){
return originPrice - 20
}
return originPrice * 0.9
}
//大促价
function onSale(originPrice){
if(originPrice >= 100 ){
return originPrice - 30
}
return originPrice * 0.8
}
//返厂价
function back(originPrice){
if(originPrice >= 100 ){
return originPrice - 20
}
return originPrice * 0.9
}
//尝鲜价
function fresh(originPrice){
return originPrice * 0.5
}
function askPrice(tag,originPrice){
// 处理预热价
if(tag === 'pre') {
return pre(originPrice)
}
// 处理大促价
if(tag === 'onSale') {
return onSale(originPrice)
}
// 处理返场价
if(tag === 'back') {
return back(originPrice)
}
// 处理尝鲜价
if(tag === 'fresh') {
return fresh(originPrice)
}
}
虽然做到了一个函数只做一件事,但是如果新增别的功能时候拓展起来就有点麻烦了,我们找找共性整合一下。
让我们拿出法宝:对象映射!
const priceProcessor = {
pre: (originPrice) => {
if (originPrice >= 100) {
return originPrice - 20;
}
return originPrice * 0.9;
},
onSale: (originPrice) => {
if (originPrice >= 100) {
return originPrice - 30;
}
return originPrice * 0.8;
},
back: (originPrice) => {
if (originPrice >= 200) {
return originPrice - 50;
}
return originPrice;
},
fresh: (originPrice) => {
return originPrice * 0.5;
},
}
/**问价函数 */
const askPrice = (tag,originPrice) =>{
return priceProcessor[tag](originPrice);
}
/**进行拓展 */
priceProcessor.newUser = (originPrice) => {
if...
}
5、行为型之状态模式(非常有用)
咖啡机生产咖啡的过程,一个状态模式的诞生
- 美式咖啡态(american):只吐黑咖啡
- 普通拿铁态(latte):黑咖啡加点奶
- 香草拿铁态(vanillaLatte):黑咖啡加点奶再加香草糖浆
- 摩卡咖啡态(mocha):黑咖啡加点奶再加点巧克力
好!if-else继续梭哈 (梭哈你个棒棒锤!给你一个脑瓜嘣)
const CoffeeMaker = function () {
this.state = 'init'; //咖啡机初始状态
this.changeState = (state) => {
this.state = state
if (state === 'american') {
// 这里用 console 代指咖啡制作流程的业务逻辑
console.log('我只吐黑咖啡');
} else if (state === 'latte') {
console.log(`给黑咖啡加点奶`);
} else if (state === 'vanillaLatte') {
console.log('黑咖啡加点奶再加香草糖浆');
} else if (state === 'mocha') {
console.log('黑咖啡加点奶再加点巧克力');
}
}
}
/**好家伙这他妈真能行 */
const maker = new CoffeeMaker()
maker.changeState('latte') //给黑咖啡加点奶
if-else侠是吧,赶紧似了得了!拆!
const CoffeeMaker = function () {
this.state = 'init'; //咖啡机初始状态
this.americanProcess = () => {
console.log('吐黑咖啡')
}
this.latteProcess = () => {
this.americanProcess();
console.log('加点奶')
}
this.vanillaLatteProcess = () => {
this.latteProcess();
console.log('再加香草糖浆');
}
this.mochaProcess = () => {
this.latteProcess();
console.log('再加巧克力');
}
this.changeState = (state) => {
this.state = state
if (state === 'american') {
// 这里用 console 代指咖啡制作流程的业务逻辑
this.americanProcess();
} else if (state === 'latte') {
this.latteProcess();
} else if (state === 'vanillaLatte') {
this.vanillaLatteProcess();
} else if (state === 'mocha') {
this.mochaProcess();
}
}
}
const maker = new CoffeeMaker()
maker.changeState('latte') //吐黑咖啡 加点奶
先符合单一原则,在处理开放封闭
const stateToProcessor = {
american: function () {
console.log('吐黑咖')
},
latte: function () {
this.american();
console.log('加点奶')
},
vanillaLatte: function () {
this.latte();
console.log('加香草糖浆')
},
mocha: function () {
this.latte();
console.log('加巧克力')
}
}
const CoffeeMaker = function () {
this.state = 'init'; //咖啡机初始状态
this.changeState = (state) => {
this.state = state
if (!stateToProcessor[state]) {
return
}
stateToProcessor[state]();
}
}
const maker = new CoffeeMaker()
maker.changeState('latte') //吐黑咖啡 加点奶