设计模式原理浅析
设计模式的相关原则和思想
SOLID
指代了面向对象编程和面向对象设计的五个基本原则,设计原则是设计模式的指导理论,可以规避不良的软件设计;
- SOLID浅析
- 单一功能原则(SingleResponsibilityPrinciple)
- 开放封闭原则(OpenedClosedPrinciple)
- 里式替换原则(LiskovSubstitutionPrinciple)
- 接口隔离原则(InterfaceSegregationPrinciple)
- 依赖反转原则(DependencyInversionPrinciple)
- 📢:在JS中,主要用到的设计模式是围绕
单一功能
和开放封闭
原则来展开的,其他的浅知即可
- 核心思想 →
封装变化
- 封装变化封装的正是软件中那些不稳定的因素,是一种防患于未来的行为,提前将变化抽离出来,从而也实现了后续的无限拓展
将变与不变分离,确保变化的部分灵活,不变的部分稳定
→ 封装变化- 无论是设计模式中的创建型、结构型还是行为型,都是在用自己的方式去封装不同类型的变化
- 创建型模式封装了
创建对象
过程中的变化,如将创建对象的过程进行抽离(工厂模式) - 结构型模式封装的是
对象之间组合方式
的变化,目的在于灵活的表达对象之间
的配合关系
和依赖关系
- 行为型模式则是将对象千变万化的
行为进行抽离
,确保可以更安全、更方便的对行为进行更改
工厂模式
核心是将创建对象的过程单独封装
,最终实现的效果是可以实现无脑传参
由一个工厂对象决定创建某一种产品对象的实例,主要用来创建同一类对象,打造高效的对象创建工厂
抽象工厂是一种创建型设计模式,可以通过提供一个接口来创建一系列相关或相互依赖的对象,而无需指定他们的具体类
在设计模式中,开发者主要做的是将
变与不变
找到,然后找到其中的共性和区别,有的甚至有变与变相互关联的,此时就需要将共性封装彻底
,将共性与个性分离彻底
,一般将共性封装到一个类中,然后将承载了共性和个性化的逻辑封装到同一个函数中,从而避免了多个共享和个性化的类过多和实例化逻辑过多的问题;
- 应用场景
- 有构造函数的地方
- 写了大量构造函数,调用了大量的new 一般工厂模式
js
// 共性部分 → 有可能会更多 没有将共性部分封装彻底
function Coder(name , age) {
this.name = name
this.age = age
this.career = 'coder'
this.work = ['写代码','写系分', '修Bug']
}
function ProductManager(name, age) {
this.name = name
this.age = age
this.career = 'product manager'
this.work = ['订会议室', '写PRD', '催更']
}
......
// 变化部分 共性和个性化强耦合 没有彻底分离 且后续拓展需要实例化跟多的共性部分然后再根据变化进行耦合起来
function Factory(name, age, career) {
switch(career) {
case 'coder':
return new Coder(name, age)
break
case 'product manager':
return new ProductManager(name, age)
break
......
}
抽离解耦版 存在的问题 → 没有遵循开放封闭原则(对拓展开放,对修改封闭:即软件实体(类、模块、函数)可以拓展,但是不可以修改);该版本存在的问题是不是在拓展,而是在修改
js
// 共性部分 name , age, career, work
function User(name , age, career, work) {
this.name = name
this.age = age
this.career = career
this.work = work
}
// 共性间的区别:
// 1、每个字段值不一样
// 2、work需要随着career字段的变化而变化
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)
}
- 抽象工厂和简单工厂对比
抽象工厂模式是每个抽象产品派生多个具体的产品类,每个抽象工厂派生多个具体工厂类,每个具体工厂负责多个(一系列)具体产品的实例创建- 共同特点:都尝试去分离一个系统中变与不变的部分
- 不同点:在于场景的复杂度
-
简单工厂:处理的对象是类,且都比较简单,其共性都已抽离,逻辑简单,可以不苛求拓展性
-
抽象工厂:处理的也是类,但是是比较复杂的类,其不同点较多,可以划分出很多不同点来,同时其可拓展的可能性也很大;主要包括四个关键角色
- 包含
抽象工厂
、具体工厂
、抽象产品
、具体产品
- 实际案例分析:创建同一品牌的多个产品,就可以使用抽象工程模式
- 抽象工厂:定义了所有所有电器品牌具有的共同接口
- 具体工厂:定义了每一个自己的电器品牌,可以生产自己的电视机、冰箱、空调等
- 抽象工厂(抽象类,不能被用于生成具体的实例),用于
声明
最终目标的共性
,在一个系统里,抽象工厂可以有多个,如手机抽象类、平板抽象类、游戏机抽象类等
jsclass MobilePhoneFactory { // 提供操作系统的接口 createOS(){ throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!"); } // 提供硬件的接口 createHardWare(){ throw new Error("抽象工厂方法不允许直接调用,你需要将我重写!"); } }
- 具体工厂(用于生产具体的产品),继承自抽象工厂、
实现了
抽象工厂里声明的那些方法
,用于创建具体的产品类
js// 具体工厂继承自抽象工厂 class FakeStarFactory extends MobilePhoneFactory { // 像如下的采用new的方式new出具体对象的类叫做具体的产品类,不同的具体产品类往往有着共同的功能,如Android和iOS都是操作系统,都有着操作手机硬件系统的具体功能 createOS() { // 提供安卓系统实例 return new AndroidOS() } createHardWare() { // 提供高通硬件实例 return new QualcommHardWare() } }
- 抽象产品(抽象类,不能被用于生成具体实例)
js// 定义操作系统这类产品的抽象产品类 class OS { controlHardWare() { throw new Error('抽象产品方法不允许直接调用,你需要将我重写!'); } } // 定义手机硬件这类产品的抽象产品类 class HardWare { // 手机硬件的共性方法,这里提取了"根据命令运转"这个共性 operateByOrder() { throw new Error('抽象产品方法不允许直接调用,你需要将我重写!'); } }
- 具体产品(用于生成产品簇里的一个具体的产品所依赖的更细粒度的产品)
js// 定义具体操作系统的具体产品类 class AndroidOS extends OS { controlHardWare() { console.log('我会用安卓的方式去操作硬件') } } class AppleOS extends OS { controlHardWare() { console.log('我会用🍎的方式去操作硬件') } } // 定义具体硬件的具体产品类 class QualcommHardWare extends HardWare { operateByOrder() { console.log('我会用高通的方式去运转') } } class MiWare extends HardWare { operateByOrder() { console.log('我会用小米的方式去运转') } } ...
JS// 这是我的手机 const myPhone = new FakeStarFactory() // 让它拥有操作系统 const myOS = myPhone.createOS() // 让它拥有硬件 const myHardWare = myPhone.createHardWare() // 启动操作系统(输出'我会用安卓的方式去操作硬件') myOS.controlHardWare() // 唤醒硬件(输出'我会用高通的方式去运转') myHardWare.operateByOrder()
- 后续的拓展就可以根据自己拓展的具体情况来判断是添加具体的产品还是具体的工厂类的新加,可以实现
开放封闭
原则(对拓展开放
,对修改封闭
) - 函数的方式实现抽象工厂
js// 创建抽象工厂函数 function createButtonFactory() { // 创建对象字典 const factories = { 'primary': createPrimaryButton, 'secondary': createSecondaryButton, }; // 创建工厂选择器函数 function createButton(type) { const factory = factories[type]; if (!factory) { throw new Error(`Invalid button type: ${type}`); } return factory(); } // 创建具体的工厂函数 function createPrimaryButton() { const button = document.createElement('button'); button.className = 'btn btn-primary'; button.textContent = 'Primary Button'; return button; } function createSecondaryButton() { const button = document.createElement('button'); button.className = 'btn btn-secondary'; button.textContent = 'Secondary Button'; return button; } // 返回工厂选择器函数 return createButton; } // 使用抽象工厂函数创建不同类型的按钮 const createButton = createButtonFactory(); const primaryButton = createButton('primary'); const secondaryButton = createButton('secondary');
jsclass FactoriesRule {//抽象工厂 constructor(){ if(new.target === FactoriesRule){ throw new Error("抽象工厂不能被实例化") } } get(type){ throw new Error("抽象工厂的方法不能被调用") } } class Factories extends FactoriesRule{//总工厂 constructor(){ super(); } get(type){ var obj = { student:new Student(), teacher:new Teacher(), } if(obj[type]){ return obj[type]; }else { throw new Error("不存在该工厂") } } } class Rule {//抽象工厂 constructor(){ if(new.target === Rule){ throw new Error("抽象工厂不能被实例化") } } getList(){ throw new Error("抽象工厂的方法不能被调用") } } class Student extends Rule{ constructor(){ super() } getList(){ return ["小明","小刚","小美","小丽"]; } } class Teacher extends Rule{ constructor(){ super() } getList(){ return ["赵老师","钱老师","孙老师","李老师"]; } } const factories = new Factories();//总工厂 const student = factories.get("student");//学生工厂 console.log(student.getList());//学生名单 const teacher = factories.get("teacher");//老师工厂 console.log(teacher.getList());//老师名单 // 拓展 class ShiYan extends FactoriesRule{//实验小学工厂 constructor(){ super(); } get(type){ var obj = { student:new Student(), teacher:new Teacher(), } if(obj[type]){ return obj[type]; }else { throw new Error("不存在该工厂") } } } const shiyan = new ShiYan();//总工厂 const shiyanStudent = shiyan.get("student");//学生工厂 console.log(shiyanStudent.getList());//学生名单 const shiyanTeacher = shiyan.get("teacher");//老师工厂 console.log(shiyanTeacher.getList());//老师名单
- 包含
-
new.target浅析
new.target
允许检测函数或构造方法是否是通过new 运算符
调用的,若函数或构造方法是由new调用的,则new.target
属性值指向该函数或构造函数,否则为undefined
-
普通函数调用,
new.target
值为undefined
,使用new
运算符调用的函数其值为函数本身,可以用new.target
来判断一个函数是否为new
调用的jsfunction Person() { if(new.target) { // new 方式调用 this.name = 'Mike' console.log(this) }else { // 普通方式调用 throw new Error('函数必须使用new调用') } } new Person() // Person { name: 'Mike' } Person() // Uncaught Error: 函数必须使用new调用
-
ES6中的class类创建的实例必须使用
new
,new.target
的值为类定义本身
js
class Person {
constructor() {
console.log(new.target,222,new.target === Person)
}
}
// sudo
// class Person {
// constructor() {
// console.log(new.target,222,new.target === Person)
// }
// } 222 true
- 在类的继承中,
new.target
的值指向初始化类的类定义(父类中的constructor的new.target指向子类而非父类)
js
class Person {
constructor() {
console.log(new.target === Person,3333,new.target === Male)
}
}
class Male extends Person {
constructor() {
super()
console.log(new.target === Person,2222,new.target === Male)
}
}
new Person()
// true 3333 false
new Male()
// false 3333 true
// false 2222 true