23种设计模式分类

一、创建型模式
  工厂模式、抽象工厂、单例模式、建造者模式、原型模式
二、结构型模式
  装饰器模式、适配器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
三、行为型模式
  策略模式、观察者模式、模板方法模式、责任链模式、迭代子模式、备忘录模式、命令模式、访问者模式、中介者模式、状态模式、解释器模式

SOLID五大设计原则:

  1. 单一职责原则(Single Responsibility Principle)单一原则要求一个类只做一件事情,并且将这件事情做好
  2. 开放封闭原则(Opened Closed Principle)对于扩展是开放的,对于修改是封闭的。也就是说,在不修改现有代码的情况下,通过扩展已有代码的方式来实现新的功能或需求
  3. 里式替换原则(Liskov Substitution Principle)任何使用父类对象的地方,都可以用其子类对象来代替,而且不会产生意想不到的结果
  4. 接口隔离原则(Interface Segregation Principle)客户端不应该依赖于它不需要的接口。也就是说,将复杂的接口进行拆分,让客户端只依赖需要的部分
  5. 依赖反转原则(Dependency Inversion Principle)这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

创造型模式

构造器模式

function Employee(name,age){
this.name = name;
this.age =age;
this.say = function(){
console.log(this.name+"-",this.age)
}
}
new Employee("kerwin",100)
new Employee("tiechui",18)

原型模式

就是将方法挂载再原型上,提高效率,class的方法默认就是挂载再原型上的

classEmployee{
constructor(name,age){
this.name= name
this.age= age
}
say(){
console.log(this.name+"-"+this.age)
}
}
var employee1 = new Employee("kerwin",100)
varemployee2 = newEmployee("shary",88)

工厂模式

根据不同的参数,生成不同的对象。
简单工厂的优点在于,你只需要一个正确的参数,就可以获取到你所需要的对象,而无需知道其创建的具体细节。但是在函数内包含了所有对象的创建逻辑和判断逻辑的代码,每增加新的构造函数还需要修改判断逻辑代码。当我们的对象不是上面的3个而是10个或更多时,这个函数会成为一个庞大的超级函数,便得难以维护。所以,简单工厂只能作用于创建的对象数量较少,对象的创建逻辑不复杂时使用

//举例:权限管理,不同用户能访问的页面是不一样的,根据不同的角色赋予不同的权限。
classUser{
constructor(role, pages) {
this.role= role
this.pages= pages
}

staticUserFactory(role) {
switch(role) {
case"superadmin":
returnnewUser("superadmin", ["home", "user-manage", "right-manage", "news-manage"])
break;
case"admin":
returnnewUser("admin", ["home", "user-manage", "news-manage"])
break;
case"editor":
returnnewUser("editor", ["home", "news-manage"])
break;
default:
thrownewError("参数错误")
}
}
}
varuser= User.UserFactory("superadmin")

抽象工厂模式

将不同类的相同属性和方法定义成父类,子类继承父类并维护自己的属性和方法,在将子类做一层抽象封装,根据不同的角色获取不同的子类的构造函数,再进行实例化。

//权限管理举例
classUser{
constructor(name,role,pages){
this.name= name
this.role= role
this.pages= pages
}

welcome(){
console.log("欢迎回来",this.name)
}

dataShow(){
//abstract
thrownewError("抽象方法需要被实现")
}
}

classSuperAdminextendsUser{
constructor(name){
super(name, "superadmin", ["home", "user-manage", "right-manage", "news-manage"])
}

dataShow(){
//abstract
console.log('superadmin-datashow')
}

addRight(){

}

addUser(){

}
}

classAdminextendsUser{
constructor(name){
super(name, "admin", ["home", "user-manage", "news-manage"])
}

dataShow(){
//abstract
console.log('admin-datashow')
}

addUser(){

}
}

classEditorextendsUser{
constructor(name){
super(name, "editor", ["home", "news-manage"])
}

dataShow(){
//abstract
console.log('editor-datashow')
}
}

functiongetAbstractUserFactory(role){
switch(role){
case"superadmin":
returnSuperAdmin

case"admin":
returnAdmin
case"editor":
returnEditor
default:
thrownewError("参数错误")
}
}

letUserClass= getAbstractUserFactory("superadmin")
// console.log(userClass)
letuser= newUserClass("kerwin")
// user.welcome()

建造者模式

工厂模式关注的是结果,只要出来个对象或者构造函数就行。
建造者模式关注的是过程和细节。
建造者模式(builder pattern)属于创建型模式的一种,提供一种创建复杂对象的方式。它将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式将一个复杂对象的构建层与其表示层相互分离,同样的构建过程可采用不同的表示。 工厂模式主要是为了创建对象实例或者类簇(抽象工厂),关心的是最终产出(创建)的是什么,而不关心创建的过程。而建造者模式关心的是创建这个对象的整个过程,甚至于创建对象的每一个细节。

classNavbar{
init() { //初始化过程
console.log("navbar-init")
}
getData() { //异步获取数据
console.log("navbar-getData")
returnnewPromise((resolve) =>{
setTimeout(() =>{
resolve("navbar-1111")
}, 1000)
})
}
render() { //拿到数据渲染
console.log("navbar-render")
}
}

classList{
init() {
console.log("List-init")
}
getData() {
console.log("List-getData")
returnnewPromise((resolve) =>{
setTimeout(() =>{
resolve("list-1111")
}, 1000)
})
}
render() {
console.log("List-render")
}
}

classCreator{ //建造者模式的优势,在这里轻松处理异步问题
asyncstartBuild(builder) {
awaitbuilder.init()
letres= awaitbuilder.getData() //关注细节
console.log(res)
awaitbuilder.render()
}
}

constop= newCreator()
op.startBuild(newNavbar())
op.startBuild(newList())

单例模式

保证一个类只有一个实例,减少命名冲突和变量污染问题,有两种方式,
饿汉式:类加载的时直接静态方法创建好对象,不管有没有用到
懒汉式:懶汉式,只有当用戶使用getInstance時,才返回cat对象,存在则返回不存在则创建
饿汉式VS懒汉式

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
    //饿汉式  看Java改写
    classEagerSingleton{
    constructor() {
    console.log('构造器被调用了');
    }
    staticinstance= newEagerSingleton()

    staticgetInstance() {
    returnEagerSingleton.instance;
    }
    }

    // 使用方法
    constobj1= EagerSingleton.getInstance();
    constobj2= EagerSingleton.getInstance();
    console.log(obj1=== obj2); // true,说明两个对象是同一个实例
    classLazySingleton{
    constructor() {
    // 构造函数代码
    }
    staticgetInstance() {
    if(!LazySingleton.instance) {
    LazySingleton.instance= newLazySingleton(); //懒汉式
    }
    returnLazySingleton.instance;
    }
    }

    // 使用方法
    constobj1= LazySingleton.getInstance();
    constobj2= LazySingleton.getInstance();
    console.log(obj1=== obj2); // true,说明两个对象是同一个实例


    //另一种方式创建
    classSingleton{
    constructor(name,age){
    if(!Singleton.instance){
    this.name= name
    this.age=age
    Singleton.instance= this
    }
    returnSingleton.instance
    }
    }
    console.log(newSingleton("kerwin",100) === newSingleton("xiaoming",18) ); //true

结构型模式

装饰器模式(切片)

装饰器模式能够很好的对已有功能进行拓展,这样不会更改原有的代码,对其他的业务产生影响,这方便我们在较少的改动下对软件功能进行拓展。
举例,网站访问时,pv(page view)浏览量统计,这些统计与查询原程序不相关,所以放在装饰器中执行,分离业务逻辑与渲染逻辑
举例,给get、post请求添加token不用每个API数据都添加,使用装饰器新建一个axios方法对象,在加入before装饰器;其实使用请求拦截也可以实现。

Function.prototype.before= function(beforeFn){
var_this= this
returnfunction(){
//先进行前置函数调用
beforeFn.apply(this,arguments)
//执行原来的函数
return_this.apply(this,arguments) //返回了this 其中的this指向test
}
}

Function.prototype.after= function(afterFn){
var_this= this
returnfunction(){
varresult= _this.apply(this,arguments)
//先进行前置函数调用
afterFn.apply(this,arguments)
//执行原来的函数
returnresult
}
}

functiontest(){
console.log("1111111")
console.log(arguments); //伪数组
return111111
}

// test = new Function()
vartest1= test.before(function(){
console.log("000000")
console.log(arguments);
}).after(function(){
console.log("222222")
console.log(arguments);
})

console.log(test1('1','2'));//注意这里的括号

适配器模式(封装一层,这种编程思想很重要)

将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
适配器不会去改变实现层,那不属于它的职责范围,它干涉了抽象的过程。外部接口的适配能够让同一个方法适用于多种系统。

//假设这是官方API ,他们渲染地图的API不一致,可以使用适配器模式封装一层使API变得一致
classTencentMap{
show(){
console.log("开始渲染腾讯地图")
}
}
classBaiduMap{
display(){
console.log("开始渲染百度地图")
}
}

classTencentAdapaterextendsTencentMap{
constructor(){
super()
}
display(){
this.show()
}
}

functionrenderMap(map){
map.display()
}
renderMap(newTencentAdapater())
renderMap(newBaiduMap())

代理模式

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

classStar{
play(){
console.log("演戏")
}
}
classStarProxy{
constructor(){
this.superStar= newStar()
}
talk(price){
if(price>=10000){
this.superStar.play()
}else{
thrownewError("价钱不合适")
}
}
}

letjr= newStarProxy()
jr.talk(10000)
varstar= {
name:"tiechui",
workprice:10000
}

letproxy= newProxy(star,{
get(target,key){
if(key==="workprice"){
console.log("访问了")
}
returntarget[key]
},
set(target,key,value){
if(key==="workprice"){
console.log("设置了")
if(value>10000){
console.log("可以合作")
}else{
thrownewError("价钱不合适")
}
}
}
})
//使用代理
proxy.name
proxy.workprice
proxy.workprice= 10
proxy.workprice= 10000

模块模式

模块化模式最初被定义为在传统软件工程中为类提供私有和公共封装的一种方法。
能够使一个单独的对象拥有公共/私有的方法和变量,从而屏蔽来自全局作用域的特殊部分。这可以减少我们的函数名与在页面中其他脚本区域内定义的函数名冲突的可能性。
ES6之前使用闭包实现,模块化模式,将私有变量放在闭包中减少污染
其实就是es6 的 import和export 模块化 ,因为模块中的属性不导出则无法访问

桥接模式

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
使用场景:一个类存在两个或多个独立变化的维度,且这两个维度都需要进行扩展
优点:
把抽象与实现隔离开,有助于独立地管理各组成部分。
缺点:
每使用一个桥接元素都要增加一次函数调用,这对应用程序的性能会有一些负面影响——提高了系统的复杂程度。
使用场景:将动画效果封装,使用桥接的方式调用,做一套动画库,模态框的时候可以这样分开

//举例 发动机与平台版本对应
functionAodi1(engine){
this.engine= engine
}
Aodi1.prototype.platform= function(){
console.log("aodi1 平台")
}
Aodi1.prototype.loadEngine= function(){
this.engine.run()
}

functionAodi2(engine){
this.engine= engine
}
Aodi2.prototype.platform= function(){
console.log("aodi2 平台")
}
Aodi2.prototype.loadEngine= function(){
this.engine.run()
}


functionV6(){
this.run= function(){
console.log("v6发动机")
}
}
functionV8(){
this.run= function(){
console.log("v8发动机")
}
}

letaodi1= newAodi1(newV6())
letaodi2= newAodi1(newV8())
letaodi3= newAodi2(newV8())

aodi1.loadEngine()
aodi2.loadEngine()
aodi3.loadEngine()

组合模式

组合模式在对象间形成树形结构;
组合模式中基本对象和组合对象被一致对待;
无须关心对象有多少层, 调用时只需在根部进行调用;

constFolder= function(folder){
this.folder= folder
this.list= []//保存 子文件夹 或者文件
}

Folder.prototype.add= function(res){
this.list.push(res)
}

Folder.prototype.scan= function(){
console.log("扫描文件夹",this.folder)
for(leti= 0;i<this.list.length;i++){
this.list[i].scan()
}
}

constFile= function(file){
this.file= file
}
File.prototype.scan= function(){
console.log("开始扫描文件",this.file)
}


//根
letrootFolder= newFolder("root")
//子文件夹
lethtmlFolder= newFolder("html")
letcssFolder= newFolder("css")
letjsFolder= newFolder("js")

//文件
lethtml4= newFile("html4")
lethtml5= newFile("html5")
letcss2= newFile("css2")
letcss3= newFile("css3")
letes5= newFile("es5")
letes6= newFile("es6")

rootFolder.add(htmlFolder)
rootFolder.add(cssFolder)
rootFolder.add(jsFolder)

htmlFolder.add(html4)
htmlFolder.add(html5)

cssFolder.add(css2)
cssFolder.add(css3)

jsFolder.add(es5)
jsFolder.add(es6)

//调用
rootFolder.scan()

行为型模式

策略模式(减少if)

我在看板开发中已经在使用策略模式了,groupName维护的部分,后来我又改成数组了,因为要顺序排列
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
该模式主要解决在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。它的优点是算法可以自由切换,同时可以避免多重if...else判断,且具有良好的扩展性

letstrategry= {
'A':(salary)=>{
returnsalary*4
},
"B":(salary)=>{
returnsalary*3
},
"C":(salary)=>{
returnsalary*2
}
}

functioncalBonus(level,salary){
returnstrategry[level](salary) //代替if else
}

console.log( calBonus("A",10000));
console.log( calBonus("C",8000));

观察者模式

当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题
优势:目标者与观察者,功能耦合度降低,专注自身功能逻辑;观察者被动接收更新,时间上解耦,实时接收目标者更新状态。
缺点:观察者模式虽然实现了对象间依赖关系的低耦合,但却不能对事件通知进行细分管控,如 “筛选通知”,“指定主题事件通知” 。

classSubject{
constructor(){
this.observers= []
}
add(observer){
this.observers.push(observer)
}
remove(observer){
this.observers= this.observers.filter(item=>item!==observer)
}
notify(){
this.observers.forEach(item=>{
// console.log(item)
item.update()
})
}
}

classObserver{
constructor(name){
this.name= name
}
update(){
console.log("update",this.name)
}
}

constsubject= newSubject()
constobserver1= newObserver("kerwin")
constobserver2= newObserver("tiechui")

subject.add(observer1)
subject.add(observer2)

setTimeout(()=>{
subject.remove(observer2)
},1000)

setTimeout(()=>{
subject.notify()
},2000)

缺点:观察者模式虽然实现了对象间依赖关系的低耦合,但却不能对事件通知进行细分管控,如 “筛选通知”,“指定主题事件通知” 。

发布订阅模式

解决观察者模式的缺点。
1.观察者和目标要相互知道
2.发布者和订阅者不用互相知道,通过第三方(调度中心)实现调度,属于经过解耦合的观察者模式
de69b727-ff90-4f1c-95b4-0463de962dbf.jpg

//publish 发布
//subscribe 订阅
const PubSub = {
message:{},
publish(type,data){
// this.list.forEach(item=>item())
if(!this.message[type]) return

this.message[type].forEach(item=>item(data))
},
subscribe(type,cb){
if(!this.message[type]){
this.message[type] = [cb]
}else{
this.message[type].push(cb)
}
},

unsubscribe(type,cb){
if(!this.message[type]) return

if(!cb){
//取消所有当前type事件
this.message[type] && (this.message[type].length=0)
}else{
this.message[type]= this.message[type].filter(item=>item!==cb)
}
}
}

function testA(data){
console.log("testA",data)
}

function testB(data){
console.log("testB",data)
}

function testC(data){
console.log("testC",data)
}

PubSub.subscribe("A",testA)
PubSub.subscribe("A",testC)
PubSub.subscribe("B",testB)

PubSub.publish('A','hi') //触发事件

命令模式

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。需要一种松耦合的方式来设计程序,使得发送者和接收者能够消除彼此之间的耦合关系。

命令模式由三种角色构成:

  1. 发布者 invoker(发出命令,调用命令对象,不知道如何执行与谁执行);
  2. 接收者 receiver (提供对应接口处理请求,不知道谁发起请求);
  3. 命令对象 command(接收命令,调用接收者对应接口处理发布者的请求)。
    classReceiver{
    //接受类
    execute(){
    console.log("接收者执行请求")
    }
    }

    classCommand{
    constructor(receiver){
    this.receiver= receiver
    }
    //命令类
    execute(){
    console.log("命令对象=>接受者如何处理")
    this.receiver.execute()
    }
    }

    classInvoker{
    //发布类
    constructor(command){
    this.command=command
    }
    order(){
    console.log("发布请求")
    this.command.execute()
    }
    }

    constorder= newCommand(newReceiver())
    constclient= newInvoker(order)
    client.order()

宏命令模式=组合模式+命令模式

特别适合用在多任务问题,其实是多个封装,很简单就能使用到

//一组命令的集合
classMacroCommand{
constructor(){
this.list= [] //子命令对象
}
add(command){
this.list.push(command)
}
execute(){
for(letitemofthis.list){
item.execute()
}
}
}

constTabs= {
execute(){
console.log("选项卡执行")

this.init()
this.getData()
this.render()
},

init(){
console.log("init")
},
getData(){
console.log("getData")
},
render(){
console.log("render")
}
}
constSwipe= {
execute(){
console.log("轮播执行")
}
}

constmacroCommand= newMacroCommand()
macroCommand.add(Tabs)
macroCommand.add(Swipe)

macroCommand.execute()

模板方法模式

模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,并且可以选择重写父类的方法。TS中abstrct可以实现

varContainer= function(params={}) {
varF= function() {}

F.prototype.init=asyncfunction() {
varlist=awaitthis.getData()
this.render(list)
}

F.prototype.getData=params.getData|| function() {
thrownewError("必须传入getData")
}
F.prototype.render= function(list) {
console.log("render",list)
}

returnF
}

varMyclass= Container({
getData(){
console.log("获取nowplaying")
return[1,2,3]
}
})

newMyclass().init()

varMyclass2= Container({
getData(){
console.log("获取comingsoon")
return[4,5,6]
}
}) //不传参数就会报错

newMyclass2().init() //异步执行

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

varobj= {  //对象部署迭代器接口
code:200,
name:"kerwin",
list:["aaaa", "bbbb", "ccc"],
[Symbol.iterator]:function() {
letindex= 0
return{
next:() =>{
if(index< this.list.length) {
return{
value:this.list[index++],
done:false
}
}else{
return{
value:undefined,
done:true
}
}
}
}
}
}
// var it = obj[Symbol.iterator]()
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())


for(letiofobj){
console.log(i)
}

ES6迭代器:内置跌迭代器Array map,set ,String ,arguments ,NodeList 可以直接使用 for of取出

vararr= ["kerwin","xiaoming","tiechui"]
console.log(arr)
// for(let i of arr){
// console.log(i)
// }

letit= arr[Symbol.iterator]() //取出迭代器对象
console.log(it)
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())


varobj= {
0:"kerwin",
1:"tiechui",
2:"xiaoming",
length:3, //必须有长度
[Symbol.iterator]:Array.prototype[Symbol.iterator]
//Symbol.iterator属性赋值为迭代器用[]包一下 当成变量而不是字符串;这里做得和数组的迭代器一样所以直接赋值数组的迭代器
}

for(letiofobj){
console.log(i)
}

责任链模式(减少if)

使多个对象都有机会处理请求,从而避免了请求的发送者与多个接收者直接的耦合关系,将这些接收者连接成一条链,顺着这条链传递该请求,直到找到能处理该请求的对象。

<inputtype="text"id="input">
<buttonid="btn">注册</button>
<script>
btn.onclick= function() {
// console.log(input.value)
checks.check()
}

functioncheckEmpty() {
if(input.value.length=== 0) {
console.log("这里不能为空")
return
}
return"next"
}

functioncheckNumber() {
if(Number.isNaN(+input.value)) {
console.log("这里必须是数字")
return
}
return"next"
}

functioncheckLength() {
if(input.value.length< 6) {
console.log("这里必须要大于6个数字")
return
}
return"next"
}

classChain{
constructor(fn) {
this.checkRule= fn
this.nextRule= null
}

addRule(nextRule) {
this.nextRule= newChain(nextRule)
returnthis.nextRule
}

end(){
this.nextRule= {
check:()=>"end"
}
}

check() {
this.checkRule() === "next"? this.nextRule.check() : null
}
}

constchecks= newChain(checkEmpty)
checks.addRule(checkNumber).addRule(checkLength).end()
</script>

优点:

  1. 符合单一职责,使每个方法中都只有一个职责。
  2. 符合开放封闭原则,在需求增加时可以很方便的扩充新的责任。
  3. 使用时候不需要知道谁才是真正处理方法,减少大量的 if 或 switch 语法。
    缺点:
  4. 团队成员需要对责任链存在共识,否则当看到一个方法莫名其妙的返回一个 next 时一定会很奇怪。
    出错时不好排查问题,因为不知道到底在哪个责任中出的错,需要从链头开始往后找。

总结

学习设计模式是一项重要的任务,因为它们是解决常见软件设计问题的经典解决方案。设计模式提供了一种通用的语言和思维框架,帮助我们构建可重用、可维护和可扩展的软件系统。通过学习设计模式,我们可以更好地理解面向对象编程的原则和概念,提高代码的质量和可读性。设计模式分为创建型、结构型和行为型三类,每个模式都有特定的用途和适用场景。掌握设计模式需要不断的实践和经验积累,只有在实际项目中应用它们,才能真正体会到它们的价值。


源码地址如果你觉得本文对你有所帮助,别忘记给我点个start,有任何疑问和想法,欢迎在评论区与我交流。