本文主要为大家介绍javascript的23种设计模式的总结。有需要的朋友可以借鉴一下,希望能有所帮助。祝大家进步很大,早日升职加薪。
目录
一、设计模式介绍。设计模式的五个设计原则。三类设计模式。二。设计模式1。工厂模式2。抽象工厂模式3。构建器模式4。单一模式模式5。适配器模式6。装饰模式7。代理模式8。外观模式9。发布-订阅模式10。迭代器模式11。状态模式12。策略模式13。命令模式14。构图模式15。模块方法模式16。分享-分享模式17。责任链模式18。中介模式18。
一、设计模式介绍
什么是设计模式?
设计模式是一种解决问题的思路,与语言无关。在面向对象软件设计的工程中,对特定问题的简单而优雅的解决方案。一般来说,设计模式是在特定场景下对特定问题的解决方案。通过设计模式,可以增加代码的可重用性、可扩展性和可维护性,最终使我们的代码具有高内聚和低耦合性。
设计模式的五大设计原则
单一职责:一个程序只需要做好一件事。如果功能太复杂,就拆分,保证各部分的独立性。开与关的原则:开对扩,关对改。当增加需求时,扩展新代码而不是修改源代码。这是软件设计的终极目标。李希特替换原理:子类可以覆盖父类,父类能出现的地方也能出现子类。界面独立原则:保持界面独立,避免“胖界面”。这是目前在TS中使用的。依赖原则:面向接口的编程,依赖抽象而非具体。消费者只关注接口,不关注具体类的实现。俗称“鸭式”
设计模式的三大类
创建:工厂模式、抽象工厂、构建者模式、单例模式、原型模式;结构模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、元共享模式;行为模式:策略模式、模板方法模式、发布和订阅模式、迭代器模式、责任链模式、命令模式、纪念模式、状态模式、访问者模式、中介者模式、解释。
二、设计模式
1.工厂模式
工厂模式是一种用于创建对象的常见设计模式。如果不暴露创建对象的具体逻辑,而是封装了逻辑,就可以称之为工厂。工厂模式也称为静态工厂模式。工厂对象决定创建一个类的实例。
优势
当调用者创建一个对象时,只要知道它的名字,它就可以是高度可扩展的。如果要添加新产品,可以直接扩展一个工厂类。隐藏产品的具体实现,只关心产品的界面。
劣势
每增加一个产品,就需要增加一个特定的类,无形中增加了系统内存的压力和系统的复杂度,也增加了对特定类的依赖。
例子
一个服装工厂可以生产不同类型的衣服,我们通过一个工厂方法类来模拟输出。
级羽绒服{
生产(){
Console.log(“生产羽绒服”)
}
}
类内衣{
生产(){
Console.log('生产内衣')
}
}
班级t恤{
生产(){
Console.log('生产t恤')
}
}
//工厂类
class clothingFactory {
构造函数(){
羽绒服
this .内衣=内衣
这件t恤衫
}
getFactory(clothingType){
const _ production=new this[clothing type]
return _production.production()
}
}
const clothing=new clothing factory()
clothing . get factory(' T _ shirt ')//生产t恤
2.抽象工厂模式
抽象工厂通过类抽象使业务适合于产品集群的创建,但不负责某类产品的实例。抽象工厂可以看作是普通工厂的升级版,主要基于生产实例,抽象工厂的目的是生产工厂。
优势
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的创造者里加代码,又要在具体的里面加代码。
例子
同样基于上面的例子,模拟出一个抽象类,同时约束继承子类的方法实现。最后再通过工厂函数返回指定的类簇
/* 抽象类
射流研究…中摘要是个保留字,实现抽象类只能通过新目标进行验证,
防止抽象类被直接实例,另外如果子类没有覆盖指定方法,则抛出错误
*/
类生产流{
构造函数(){
如果(新。目标===生产流程){
抛出新错误('抽象类不能被实例)
}
}
生产(){
抛出新错误('生产要被重写)
}
材料(){
抛出新错误('材料要被重写)
}
}
级羽绒服扩展生产流程{
生产(){
控制台。日志(` o材料:${this.materials()},生产羽绒服`)
}
材料(){
'返回'鸭毛'
}
}
级内衣拓展生产流程{
生产(){
控制台。日志(` o材料:${this.materials()},生产内衣`)
}
材料(){
'返回'丝光棉'
}
}
t恤类扩展生产流程{
生产(){
控制台。日志(` o材料:${this.materials()},生产t恤`)
}
材料(){
'返回'纯棉'
}
}
函数getAbstractProductionFactory(服装类型){
const clothingObj={
羽绒服:羽绒服,
内衣:内衣,
t恤:t恤衫,
}
if(clothingObj[clothingType]){
返回clothingObj
}
抛出新错误(` 1工厂暂时不支持生产这个${clothingType}类型的服装`)
}
const downkjacketclass=getAbstractProductionFactory('羽绒服')
const under wear class=getAbstractProductionFactory('内衣)
const羽绒服=新羽绒服类()
常数内衣=新内衣类()
downJacket.production() //材料:鸭毛,生产羽绒服
内衣。生产()//材料:丝光棉,生产内衣
3.建造者模式
建造者模式是一种比较复杂使用频率较低的创建型设计模式,建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。主要用于将一个复杂对象的构建与他的表现分离,使得同样的构建过程可以创建不同的表示。
优点
建造者独立易扩展方便控制细节风险
缺点
产品必须有共同点,范围有限制当内部有变化复杂时,会有很多建造类
例子
下面继续用服装厂的生产流程作为例子。
//抽象类
班级服装{
构造函数(){
this.clothingType=' '
这个价格
}
}
内衣类延伸服装{
构造函数(){
超级()
this.clothingType='内衣'
这个。价格=10英镑
}
}
t恤延伸类服装{
构造函数(){
超级()
this.clothingType=' t _恤'
这个。价格=50
}
}
类羽绒服延伸服装{
构造函数(){
超级()
this.clothingType='DownCoat '
这个。价格=500
}
}
//产品
类别购买{
构造函数(){
this.clothings=[]
}
添加服装(服装){
这。衣服。推(衣服)
}
countPrice() {
返回this . cloths . reduce((prev,cur)=cur.price prev,0)
}
}
//厂长
工厂经理类别{
创造内衣(){
抛出新错误(` 1子类必须重写创建内衣`)
}
createTShirt() {
抛出新错误(` 1子类必须重写createTShirt `)
}
createDownCoat() {
抛出新错误(` 1子类必须重写下降`)
}
}
//工人
工人阶级扩展工厂经理{
构造函数(){
超级()
this.purchase=new Purchase()
}
创建内衣(数量){
对于(设I=0;i numi ) {
this.purchase.addClothing(新内衣())
}
}
创意t恤(编号){
对于(设I=0;i numi ) {
这个。购买。添加服装(新t恤())
}
}
createDownCoat(编号){
对于(设I=0;i numi ) {
this . purchase . add clothing(new down coat())
}
}
}
//销售
类别销售人员{
构造函数(){
this.worker=null
}
setWorker(worker) {
工人=工人
}
储备(服装){
clothing.forEach((item)={
if (item.type==='内裤'){
this . worker . create内衣(item.num)
} else if(item . type===' t _ shirt '){
this . worker . create t shirt(item . num)
} else if(item . type===' down coat '){
this . worker . create down coat(item . num)
}否则{
尝试{
抛出新错误(“公司暂时不生产或不存在此类商品”)
} catch(错误){
console.log(错误)
}
}
});
const purchase=this . worker . purchase
return purchase.countPrice()
}
}
const salesman=新销售员()
const worker=new Worker()
销售员。setWorker(工人)
常量顺序=[
{
类型:“内衣”,
数字:10
},
{
类型:'t恤',
数字:4
},
{
类型:“向下覆盖”,
数字:1
}
]
Console.log(`此订单所需金额:$ {salesman.reserve (order) } `)
4.单例模式
Singleton模式的思路是保证一个类只能实例化一次,每次获取时,如果类已经创建了实例,就直接返回实例,否则就创建一个实例保存返回。singleton模式的核心是创建一个唯一的对象,但是用javascript创建一个唯一的对象太简单了。为了得到一个对象而创建一个类,有点多余。比如const obj={},obj是唯一对象,在全局作用域的声明下可以在任何地方访问,满足singleton模式的条件。
优势
内存中只有一个实例,减少了内存开销。从而避免了资源的多重占用。
劣势
违反单一职责,一个类应该只关心内部逻辑,而不关心外部实现。
例子
我们经常看到的登录弹窗,不是显示就是隐藏,不可能同时出现两个弹窗。让我们通过一个类来模拟弹出窗口。
类登录框架{
静态实例=空
构造者(州){
这个州=州
}
show(){
if(this.state==='show'){
Console.log(“显示登录框”)
返回
}
this.state='show '
Console.log(“登录框显示成功”)
}
hide(){
if(this.state==='hide'){
Console.log(“登录框是隐藏的”)
返回
}
this.state='hide '
Console.log(“登录框成功隐藏”)
}
//通过静态方法获取静态属性实例上是否有实例。如果没有创建实例,则返回它;否则,直接返回现有实例。
静态getInstance(状态){
如果(!this.instance){
this . instance=new log in frame(state)
}
返回此实例
}
}
const P1=log in frame . getinstance(' show ')
const p2=log in frame . getinstance(' hide ')
console.log(p1===p2) //true
5.适配器模式
适配器模式的目的是解决对象之间接口不兼容的问题。通过适配器模式,两个不兼容的对象在调用时可以正常工作,不需要修改源代码。
优势
让任意两个不相关的类同时有效运行,提高可重用性、透明性和灵活性。
劣势
过度使用适配器模式会使系统凌乱,整体难以控制。当适配器无法重新配置时,建议使用适配器。
例子
举个真实的例子,杰克只会说英语,小明只会说中文。他们在交流上有障碍。小红是中英文双语。通过小红把杰克的英语翻译成中文,让小明和杰克可以无障碍交流。这里小红充当了一个适配器。
千斤顶等级{
英语(){
返回“我会说英语”
}
}
小明班{
中文(){
返回“我只会说中文”
}
}
//适配器
小红班{
施工方(人){
这个人=人
}
中文(){
return `$ { this . person . English()}翻译:“我会说英语”
}
}
课堂交流{
说(语言)
console . log(language . China())
}
}
const晓明=new晓明()
const小红=新小红(新杰克())
常数通信=新通信()
communication.speak(小明)
communication.speak(小红)
6.装饰器模式
装饰者模式可以在不改变自身的情况下给源代码增加职责。比继承勋章还轻。一般来说,我们在心爱的手机上贴膜,配手机壳和贴纸。这些是手机的装饰品。
优势
装饰类和被装饰类可以相互独立发展,但不会相互耦合。装饰模式是一种继承的替代模式,可以动态扩展实现类的功能。
劣势
多层装饰会增加复杂性。
例子
在编写飞机战争的游戏中,飞机物体的攻击方式只有普通的子弹攻击。如何在不改变原代码的情况下,为其制作激光武器、导弹武器等其他攻击模式?
飞机等级{
普通(){
Console.log('发射普通子弹')
}
}
飞机装饰类{
建造商(飞机)
这架飞机=飞机
}
激光(){
Console.log('发射激光')
}
guidedMissile(){
Console.log('发射导弹')
}
普通(){
this.aircraft .普通()
}
}
const飞机=新飞机()
const aircraftDecorator=新的aircraftDecorator(飞机)
aircraft decorator . ordinal()//发射普通项目符号
AircraftDecorator.laser() //发射激光
飞机装潢师。导弹()//发射导弹
//你可以看到它已经被修饰和扩展了,没有改变源代码
7.代理模式
代理模式的关键是当客户不方便直接访问某个对象或不满足需求时,提供一个体双对象来控制对某个对象的访问。客户实际上访问的是body double对象。在主体双重处理请求之后,它将请求转发给本体对象。agent和本体之间的接口需要一致性。agent和本体的关系可以说是鸭子式的,具体怎么实现并不重要,只要它们之间暴露的方法一致就行。
优势
职责明确,扩展性高,智能化。
劣势
当在对象之间添加代理时,处理速度可能会受到影响。代理的实现需要额外的工作,有些代理可能非常复杂。
例子
众所周知,领导拥有公司的最高权力。假设公司有100个员工,如果每个人都去找领导办事,领导肯定会崩溃。所以领导雇一个秘书帮他收集整理事务,秘书会在合适的时候把要处理的业务交给老板。在这里,秘书是领导者的代理角色。
//员工
班级员工{
构造函数(affairType){
this.affairType=affairType
}
应用于(目标){
target . receiveapplyfor(this . affair type)
}
}
//秘书
班级秘书{
构造函数(){
this.leader=新领导()
}
receiveApplyFor(affair){
(事件)
}
}
//领导力
班长{
receiveApplyFor(affair){
console . log(` approved:$ { affair } `)
}
}
Const staff=新员工(“晋升和加薪”)
Staff.applyfor(新秘书())//审批:升职加薪
8.外观模式
外观模式的本质是封装交互,隐藏系统的复杂性,提供可访问的接口。集成了一组子系统接口的高级接口可以提供一致的外观,减少外界与多个子系统的直接交互,使子系统的使用更加方便。
优势
降低系统的相互依赖性,以及安全性和灵活性
劣势
违背了开放封闭的原则,有变化就改,即使继承重建也不可行,会很麻烦。
例子
外观模式通常用于处理高级浏览器和较低版本浏览器的某些界面的兼容性。
函数addEvent(el,type,fn){
If(el.addEventlistener){//高级浏览器添加事件DOM API
el.addEventlistener(type,fn,false)
}else if(el.attachEvent){//添加浏览器低版本的事件API
el.attachEvent(`on${type} ',fn)
}else {//其他
el[type]=fn
}
}
在另一种情况下,当函数中的参数可以传递或不可以传递时,该函数被重载以使参数传递更加灵活。
函数bindEvent(el,type,selector,fn){
如果(!fn){
fn=选择器
}
//其他代码
console.log(el,type,fn)
}
bindEvent(document.body,' click ',' #root ',()={})
bindEvent(document.body,' click ',()={})
9.发布订阅模式
订阅,也称为观察者模式,定义了对象之间1对N的依赖关系。当其中一个对象发生变化时,所有依赖于它的对象都会得到通知。发布-订阅模式经常出现在我们的工作场景中,比如当你将一个事件绑定到DOM时,你已经在使用发布-订阅模式了。通过订阅DOM上的click事件,您将在被点击时向订阅者发布一条消息。
优势
观察者和被观察者是抽象耦合的。并建立触发机制。
劣势
当有许多订阅者时,同时通知所有订阅者可能会导致性能问题。如果在订阅者和订阅目标之间执行循环引用,将会导致崩溃。订阅模式没有办法给订阅者提供订阅的目标,只能提供它是如何变化的。
例子
打个比喻,前段时间冬奥会项目还没开始就可以提前预定了。项目快开始的时候,APP会提前给我们发项目即将开始的通知,但是时间到了就不通知了。另外,项目还没开始的时候,可以取消订阅,避免收到通知。我们根据这个需求写个例子吧。
类别主题{
构造函数(){
this.observers={}
this.key=' '
}
添加(观察者){
const key=observer.project
如果(!this . observators[key]){
this . observators[key]=[]
}
观察者[答案]。推(观察者)
}
移除(观察者){
const _ observers=this . observers[observer . project]
console.log(_observers,11)
if(_observers.length){
_observers.forEach((item,index)=gt;{
if(item===observer){
_observers.splice(索引,1)
}
})
}
}
setObserver(subject){
this.key=主题
this.notifyAllObservers()
}
notifyAllObservers(){
这个.观察者[这个.关键]。forEach((item,index)=gt;{
item .更新()
})
}
}
课堂观察者{
施工方(项目,名称){
this.project=项目
this.name=name
}
update() {
Console.log(`亲爱的:${this.name}您的计划项目:[${this.project}]立即开始`)
}
}
const subject=新主题()
Constxiao=新观察者('滑雪','肖')
Const A=新观察者('大跳跃',' A ')
Const B=新观察者('大跳跃',' B ')
Const C=新观察者('大跳跃',' C ')
subject.add(小明)
subject.add(A)
主题.添加(B)
主题.添加(C)
Subject.remove(B) //取消订阅
Subject.setObserver('大跳转')
/* *执行结果
*亲爱的:A你预定的项目:【大跳台】马上开始。
*亲爱的:C你预定的项目:【大跳台】马上开始。
*/
10.迭代器模式
迭代器模式指的是提供一种方法来顺序访问聚合对象中的每个元素,而不暴露对象的内部。
优势
它支持以不同的方式遍历聚合对象。迭代器简化了聚合类。同一聚合上可以有多个遍历。在迭代器模式下,添加新的聚合类和迭代器类很方便,不需要修改原始代码。
劣势
因为迭代器模式将存储数据和遍历数据的职责分开,所以添加新的聚合类需要添加新的迭代器类,并且类的数量成对增加,这在一定程度上增加了系统的复杂性。
例子
迭代器分为内部迭代器和外部迭代器,各有各的适用场景。
内部迭代器
//内部迭代器表示迭代规则已经在内部定义。它完全接受整个迭代过程,对外只需要一次初始调用。
array . prototype . my each=function(fn){
for(设I=0;ithis.lengthi ){
fn(这个[我],我,这个)
}
}
array . prototype . my each=function(fn){
for(设I=0;ithis.lengthi ){
fn(这个[我],我,这个)
}
}
[1,2,3,4].MyEach((item,index)={
console.log(项目,索引)
})
外部迭代器
//外部迭代器必须显示的迭代的下一个元素。增加了调用的复杂度,但也增加了迭代器的灵活性,可以手动控制迭代过程。
类迭代器{
构造函数(arr){
this.current=0
this.length=数组长度
this.arr=arr
}
下一个(){
返回this.getCurrItem()
}
isDone(){
返回this.current=this.length
}
getcuritem(){
返回{
done:this.isDone(),
值:this.arr[this.current ]
}
}
}
let iterator=new Iterator([1,2,3])
而(!(item=iterator.next())。完成){
console.log(项目)
}
iterator.next()
/*下面的数据格式是不是很熟悉?
{完成:假,值:1}
{完成:假,值:2}
{完成:假,值:3}
{完成:真,值:未定义}
*/
11.状态模式
当对象的内部状态改变时,允许对象改变其行为。该对象似乎已经修改了它的类。更流行的方法是记录一组状态,每个状态对应一个实现。实现时,实现根据状态运行。
优势
将与某个状态相关的所有行为放在一个类中,可以方便地添加新的状态。你只需要改变对象的状态来改变对象的行为。允许状态转换逻辑与状态对象集成在一起,而不是一个巨大的条件语句块。多个环境对象可以共享一个状态对象,从而减少系统中的对象数量。
劣势
状态的使用将不可避免地增加系统类和对象的数量。模式的结构和实现是复杂的,使用不当会导致程序结构和代码的混乱。该模式不太支持“开闭原则”。在切换状态的状态模式中添加一个新的状态类,需要修改负责状态转换的人的源代码,否则就无法切换到新的状态,而修改一个状态类的行为也需要修改相应类的源代码。
例子
lol中对渡鸦Q的攻击有三次。同样的密钥,不同的状态下攻击行为是不一样的。通常,我们也可以通过if来实现.else,但这显然不利于扩展,违背了开放和封闭原则。接下来,用代码来描述这个场景。
类别状态{
构造器(攻击){
攻击=攻击
}
句柄(上下文){
console.log(this.attack)
context.setState(this)
}
}
类上下文{
构造函数(){
这个. state=null
}
getState(){
返回此状态
}
setState(state){
这个州=州
}
}
Const q1=新状态(' Q1 first strike '),
Q2=新州('第二季度罢工2 '),
Q3=新状态(“q3 Strike 3”),
context=新上下文()
Q1.handle(上下文)//q1 Strike 1
Q2.handle(上下文)//q2 Strike 2
Q3.handle(上下文)//q3 Strike 3
12.策略模式
模式是指定义一系列算法,并逐一封装。目的是把算法的使用和算法的实现分开。同时也可以用来封装一系列的规则,比如常见的表单验证规则。只要这些规则指向同一个目标,并且可以交替使用,它们就可以被策略模式封装。
优势
算法可以自由切换,避免了多级条件判断的使用,增加了可扩展性。
劣势
随着策略类的增加,所有的策略类都需要公开。
例子
刚进入这个行业的时候,经常会写不完的如果.为形式验证而写作。我意识到这种编写方式并不可靠,所以我把检查规则放在一个对象中,放在一个函数中控制,把规则和实现分开,每次只需要修改封装规则中的配置。在后面的场景中,这种方法用来解决频繁使用if的问题.否则。当我第一次接触倒写策略模式时,我意识到这种写作方法也算是一种策略模式。
常量规则={
cover_img: {
必须:假的,
消息:“请上传封面图片”,
值:“”
},
名称:{
必须:真的,
消息:“名称不能为空”,
值:“”
},
性别:{
必须:真的,
消息:“请填写性别”,
值:“”
},
生日:{
必须:假的,
'消息: '请选择生日,
值:""
},
}
函数验证(){
对于(规则中的常量键){
如果(规则[关键]。mustampamp!规则[关键字]。val){
console.log(rules[key]).味精)
}
}
}
验证()
//姓名不能为空
//请填写性别
上面的例子是以射流研究…方式写的,在Java语言(一种计算机语言,尤用于创建网站)描述语言将函数作为一等公民的语言里,策略模式就是隐形的,它已经融入到了Java语言(一种计算机语言,尤用于创建网站)描述语言的语言中,所以以Java语言(一种计算机语言,尤用于创建网站)描述语言方式的策略模式会显得简单直接。不过我们依然要了解传统的策略模式,下面来看看传统的策略模式的例子。
//html -
表单操作=' http://xxx.com/register' id='注册表'方法='post '
请输入用户名:输入类型='text' name='userName' /
请输入密码:input type=' text ' name=' password '/
请输入手机号码:input type=' text ' name=' phone number '/
按钮提交/按钮
/表单
//js -
班级策略{
构造函数(){
this.rules={}
}
添加(关键字,规则){
规则,规则
归还这个
}
}
类别验证器{
构造者(策略){
this.cache=[] //保存检验规则
策略=策略
}
添加(dom,规则){
rules.forEach((rule)={
const策略ary=规则。策略。拆分(':')
this.cache.push(()={
const策略=策略ary。shift()
strategyAry.unshift(dom.value)
strategyAry.push(rule.errorMsg)
控制台。日志(这个。策略[策略])
返回策略,策略.应用(dom,strategyAry)
})
});
}
start() {
对于(设i=0,validator func validator func=this。cache[I];) {
const msg=validatorFunc()
如果(消息){
返回消息
}
}
}
}
const登记表=文档。getelementbyid('注册表')//获取表单世界节点
常量策略=新策略()
strategies.add('isNonEmpty ',function(value,errorMsg) {
如果(!值){
返回错误消息
}
}).add('minLength ',function(value,Length,errorMsg) {
if (value.length length) {
返回错误消息
}
}).add('isMobile ',function(value,errorMsg) {
如果(!/(^1[3|5|8][0-9]{9}$)/.测试(值)){
返回错误消息
}
})
函数validataFunc() {
常量验证器=新验证器(策略.规则)
//多个校验规则
验证器。添加(注册表。用户名,[
{
策略:“isNonEmpty”,
错误消息: '用户名不能为空'
}, {
策略:"最小长度:10",
错误消息: '用户名长度不能少于10位'
}
])
验证器。添加(注册表。密码,[{
策略:"最小长度:6",
错误消息: '密码长度不能少于6位'
}])
验证器。添加(注册表。电话号码,[{
策略:“isMobile”,
错误消息: '手机号码格式不对'
}])
const errorMsg=validator.start()
返回errorMsg //返回错误信息。
}
登记表。onsubmit=function(){
const errorMsg=validataFunc()
if (errorMsg) { //如果存在错误信息,显示错误信息,并且阻止onsubmit默认事件
console.log(errorMsg)
返回错误的
}
}
13.命令模式
命令模式中的命令指的是一个执行某些特定的事情的指令。命令模式最常见的应用场景如:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时可以通过一种松耦合的方式来设计程序,使得请求发送者和请求接收者消除彼此之间的耦合关系。
优点
降低了代码的耦合度,易扩展,出现新的命令可以很容易的添加进去
缺点
命令模式使用过度会导致代码中存在过多的具体命令。
例子
假设一个项目中开发一个页面,其中一个程序员负责绘制静态页面,包括一些按钮,另一个程序员负责开发这些按钮的具体行为。负责静态页面的程序员暂时不知道这些按钮以后会怎么样。在不知道具体行为是什么的情况下,借助命令模式,可以解锁按钮与负责具体行为的对象之间的耦合。
//html -
按钮id='button2 '单击按钮1/按钮
按钮id='button2 '单击按钮2/按钮
按钮id='button3 '单击按钮3/按钮
//js -
const button 1=document . getelementbyid(' button 1 '),
button 2=document . getelementbyid(' button 2 '),
button 3=document . getelementbyid(' button 3 ');
常量菜单栏={
刷新:函数(){
Console.log(“刷新菜单目录”)
}
}
常量子菜单={
add:function(){
Console.log(“添加子菜单”)
},
del:函数(){
Console.log(“删除子菜单”)
}
}
函数setCommand(el,command){
el.onclick=function(){
命令执行()
}
}
类menubar命令{
构造者(接收者,密钥){
这个接收器=接收器
this.key=key
}
执行(){
this.receiver[this.key]()
}
}
setCommand(button1,new MenuBarCommand(MenBar,' refresh '))
setCommand(button2,new MenuBarCommand(子菜单,“添加”))
setCommand(button3,new MenuBarCommand(子菜单,“del”))
14.组合模式
模式是由一些小的子对象构成的更大的对象,而这些小的子对象本身也可能是由几个孙对象组成的。组合模式将对象组合成一个树形结构,以表示“部分-整体”层次结构。除了用于表示树形结构之外,组合模式的另一个优点是,通过对象的多态性,用户可以一致地使用单个对象和组合对象。
优势
高层模块调用简单,可以自由添加节点。
劣势
它的叶子对象和子对象声明是实现类,而不是接口,这违反了依赖倒置的原则。
例子
用我们最常见的文件夹和文件的关系,很适合用组合模式来描述。文件夹可以包含子文件夹和文件,而文件不能包含任何文件。这种关系最终会形成一棵树。下面是添加文件,扫描这个文件中的文件,删除文件。
//文件夹类
类别文件夹{
构造者(姓名){
this.name=name
this.parent=null
this.files=[]
}
//添加文件
添加(文件){
file.parent=this
this.files.push(文件)
归还这个
}
//扫描文件
扫描(){
Console.log(`开始扫描文件夹:$ {this.name } `)
this . files . foreach(file=gt;{
file.scan()
});
}
//删除指定的文件
移除(){
如果(!this.parent) {
返回
}
for (let files=this.parent.files,i=files.len
gth - 1; i >= 0; i--) { const file = files[i] if (file === this) { files.splice(i, 1) break } } } } // 文件类 class File { constructor(name) { this.name = name this.parent = null } add() { throw new Error('文件下面不能添加任何文件') } scan() { console.log(`开始扫描文件:${this.name}`) } remove() { if (!this.parent) { return } for (let files = this.parent.files, i = files.length - 1; i >= 0; i++) { const file = files[i] if (file === this) { files.splice(i, 1) } } } } const book = new Folder('电子书') const js = new Folder('js') const node = new Folder('node') const vue = new Folder('vue') const js_file1 = new File('javascript高级程序设计') const js_file2 = new File('javascript忍者秘籍') const node_file1 = new File('nodejs深入浅出') const vue_file1 = new File('vue深入浅出') const designMode = new File('javascript设计模式实战') js.add(js_file1).add(js_file2) node.add(node_file1) vue.add(vue_file1) book.add(js).add(node).add(vue).add(designMode) book.remove() book.scan()15.模块方法模式
模块方法模式是一种基于继承的设计模式,在javascript中没有真正意义上的继承,所有继承都来自原型(prototype)上的继承,随着ES6的class到来,实现了继承的“概念”,让我们可以以一种很方便简洁的方式继承,但其本质上还是原型继承。模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。抽象父类主要封装了子类的算法框架,以及实现了一些公共的方法和其他方法的执行顺序。子类通过继承父类,继承了父类的算法框架,并进行重写。优点
提供公共的代码便于维护。行为由父类控制,具体由子类来实现。缺点
其每一个具体实现都需要继承的子类来实现,这无疑导致类的个数增加,使得系统庞大。例子
拿咖啡和茶的例子来说,制作咖啡和茶都需要烧开水,把水煮沸是一个公共方法,随后的怎么冲泡,把什么倒进杯子,以及添加什么配料,它们可能各不一样,根据以上特点,开始我们的例子。 // 抽象父类 class Beverage { boilWater(){ console.log('把水煮沸') } brew(){ throw new Error('字类必须重写brew方法') } pourInCup(){ throw new Error('字类必须重写pourInCup方法') } addCondiments(){ throw new Error('字类必须重写addCondiments方法') } init(){ this.boilWater() this.brew() this.pourInCup() this.addCondiments() } } // 咖啡类 class Coffee extends Beverage { brew(){ console.log('用沸水冲泡咖啡') } pourInCup(){ console.log('把咖啡倒进杯子') } addCondiments(){ console.log('加糖和牛奶') } } // 茶类 class Tea extends Beverage { brew(){ console.log('用沸水侵泡茶叶') } pourInCup(){ console.log('把茶倒进杯子') } addCondiments(){ console.log('加柠檬') } } const coffee = new Coffee() coffee.init() const tea = new Tea() tea.init()16.享元模式
享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量的细粒度对象。如果系统中创建了大量的类似对象,会导致内存消耗过高,通过享用模式处理重用类似对象,减少内存消耗的问题,达到性能优化方案。享元模式的关键是如何区分内部状态和外部状态内部状态:可以被对象共享,通常不会改变的称为内部状态外部状态:取决于具体的场景,根据具体的场景变化,并且不能被共享的称为外部状态优点
减少了大批量对象的创建,降低了系统了内存。缺点
提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。例子
let id = 0 // 定义内部状态 class Upload { constructor(uploadType) { this.uploadType = uploadType } // 点击删除时 小于3000直接删除,大于3000通过confirm提示弹窗删除。 delFile(id) { uploadManager.setExternalState(id,this) if(this.fileSize < 3000){ return this.dom.parentNode.removeChild(this.dom) } if(window.confirm(`确定要删除该文件吗?${this.fileName}`)){ return this.dom.parentNode.removeChild(this.dom) } } } // 外部状态 class uploadManager { static uploadDatabase = {} static add(id, uploadType, fileName, fileSize) { const filWeightObj = UploadFactory.create(uploadType) const dom = this.createDom(fileName, fileSize, () => { filWeightObj.delFile(id) }) this.uploadDatabase[id] = { fileName, fileSize, dom } } // 创建DOM 并且为button绑定删除事件。 static createDom(fileName, fileSize, fn) { const dom = document.createElement('div') dom.innerHTML = ` <span>文件名称:${fileName},文件大小:${fileSize}</span> <button class="delFile">删除</button> ` dom.querySelector('.delFile').onclick = fn document.body.append(dom) return dom } static setExternalState(id, flyWeightObj) { const uploadData = this.uploadDatabase[id] for (const key in uploadData) { if (Object.hasOwnProperty.call(uploadData, key)) { flyWeightObj[key] = uploadData[key] } } } } // 定义一个工厂创建upload对象,如果其内部状态实例对象存在直接返回,反之创建保存并返回。 class UploadFactory { static createFlyWeightObjs = {} static create(uploadType) { if (this.createFlyWeightObjs[uploadType]) { return this.createFlyWeightObjs[uploadType] } return this.createFlyWeightObjs[uploadType] = new Upload(uploadType) } } // 开始加载 const startUpload = (uploadType, files)=>{ for (let i = 0, file; file = files[i++];) { uploadManager.add(++id, uploadType, file.fileName, file.fileSize) } } startUpload('plugin', [ {fileName: '1.txt',fileSize: 1000}, {fileName: '2.html',fileSize: 3000}, {fileName: '3.txt',fileSize: 5000} ]); startUpload('flash', [ {fileName: '4.txt',fileSize: 1000}, {fileName: '5.html',fileSize: 3000}, {fileName: '6.txt',fileSize: 5000} ]);17.职责链模式
职责链模式的定义是:使用多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象链成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。优点
降低耦合度,它将请求的发送者和接收者解耦。简化了对象,使得对象不需要知道链的结构。增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。缺点
不能保证每一条请求都一定被接收。系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。可能不容易观察运行时的特征,有碍于排除问题。例子
假设我们负责一个手机售卖的电商网站,分别缴纳500元和200元定金的两轮预订后,会分别收到100元和50元的优惠券,而没有支付定金的则视为普通购买,没有优惠券,并且在库存有限的情况下也无法保证能购买到。 class Order500 { constructor(){ this.orderType = 1 } handle(orderType, pay, stock){ if(orderType === this.orderType&&pay){ console.log('500元定金预约,得到100元优惠券') }else { return 'nextSuccessor' } } } class Order200 { constructor(){ this.orderType = 2 } handle(orderType, pay, stock){ if(orderType === this.orderType&&pay){ console.log('200元订金预约,得到50元优惠卷') }else { return 'nextSuccessor' } } } class OrderNormal { constructor(){ this.stock = 0 } handle(orderType, pay, stock){ if (stock > this.stock) { console.log('普通购买,无优惠卷') } else { console.log('手机库存不足') } } } class Chain { constructor(order){ this.order = order this.successor = null } setNextSuccessor(successor){ return this.successor = successor } passRequest(...val){ const ret = this.order.handle.apply(this.order,val) if(ret === 'nextSuccessor'){ return this.successor&&this.successor.passRequest.apply(this.successor,val) } return ret } } console.log(new Order500()) var chainOrder500 = new Chain( new Order500() ); var chainOrder200 = new Chain( new Order200() ); var chainOrderNormal = new Chain( new OrderNormal() ); chainOrder500.setNextSuccessor( chainOrder200 ); chainOrder200.setNextSuccessor( chainOrderNormal ); chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券 chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券 chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券 chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足18.中介模式
中介者模式的作用就是解除对象与对象之间的紧密耦合关系。增加一个中介者对象之后,所有相关对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发生改变时,只需要通过中介者对象即可。中介者使各对象之间耦合松散,而且可以独立改变他们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。优点
降低了类的复杂度,将一对多转化成了一对一。各个类之间的解耦。缺点
当中介者变得庞大复杂,导致难以维护。例子
// html----------- 选择颜色:<select name="" id="colorSelect"> <option value="">请选择</option> <option value="red">红色</option> <option value="blue">蓝色</option> </select> <br /> 选择内存:<select name="" id="memorySelect"> <option value="">请选择</option> <option value="32G">32G</option> <option value="63G">64G</option> </select> <br /> 输入购买数量:<input type="text" id="numberInput" /> <br /> <div>你选择了颜色:<span id="colorInfo"></span></div> <div>你选择了内存:<span id="memoryInfo"></span></div> <div>你选择了数量:<span id="numberInfo"></span></div> <button id="nextBtn" disabled="true">请选择手机颜色和购买数量</button> // js ------------------- const goods = { "red|32G": 3, "red|16G": 0, "blue|32G": 1, "blue|16G": 6 }, colorSelect = document.getElementById('colorSelect'), memorySelect = document.getElementById('memorySelect'), numberInput = document.getElementById('numberInput'), colorInfo = document.getElementById('colorInfo'), memoryInfo = document.getElementById('memoryInfo'), numberInfo = document.getElementById('numberInfo'), nextBtn = document.getElementById('nextBtn'), mediator = (function () { return { changed(obj) { const color = colorSelect.value, memory = memorySelect.value, number = numberInput.value, stock = goods[`${color}|${memory}`] if (obj === colorSelect) { colorInfo.innerHTML = color } else if (obj === memorySelect) { memoryInfo.innerHTML = memory } else if (obj === numberInput) { numberInfo.innerHTML = number } if (!color) { nextBtn.disabled = true nextBtn.innerHTML = '请选择手机颜色' return } if (!memory) { nextBtn.disabled = true nextBtn.innerHTML = '请选择内存大小' return } if (Number.isInteger(number - 0) && number < 1) { nextBtn.disabled = true nextBtn.innerHTML = '请输入正确的购买数量' return } nextBtn.disabled = false nextBtn.innerHTML = '放入购物车' } } })() colorSelect.onchange = function () { mediator.changed(this) } memorySelect.onchange = function () { mediator.changed(this) } numberInput.oninput = function () { mediator.changed(this) }19.原型模式
原型模式是指原型实例指向创建对象的种类,通过拷贝这些原型来创建新的对象,说白了就是克隆自己,生成一个新的对象。优点
不再依赖构造函数或者类创建对象,可以将这个对象作为一个模板生成更多的新对象。缺点
对于包含引用类型值的属性来说,所有实例在默认的情况下都会取得相同的属性值。例子
const user = { name:'小明', age:'30', getInfo(){ console.log(`姓名:${this.name},年龄:${this.age}`) } } const xiaozhang = Object.create(user) xiaozhang.name = '小张' xiaozhang.age = 18 xiaozhang.getInfo() // 姓名:小张,年龄:18 user.getInfo() // 姓名:小明,年龄:3020.备忘录模式
备忘录模式就是在不破坏封装的前提下,捕获一个对象内部状态,并在该对象之外保存这个状态,以保证以后可以将对象恢复到原先的状态。优点
给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。实现了信息的封装,使得用户不需要关心状态的保存细节。缺点
如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。例子
// 棋子 class ChessPieces { constructor(){ this.chess = {} } // 获取棋子 getChess(){ return this.chess } } // 记录棋路 class Record { constructor(){ this.chessTallyBook = [] // 记录棋路 } recordTallyBook(chess){ // console.log(this.chessTallyBook.includes(chess)) const isLoadtion = this.chessTallyBook.some( item=>item.location === chess.location ) if(isLoadtion){ console.log(`${chess.type},${chess.location}已存在其他棋子`) }else { this.chessTallyBook.push(chess) } // this.chessTallyBook.some(item=>item.location === chess.location) } getTallyBook(){ return this.chessTallyBook.pop() } } // 下棋规则 class ChessRule { constructor(){ this.chessInfo = {} } playChess(chess){ this.chessInfo = chess } getChess(){ return this.chessInfo } // 记录棋路 recordTallyBook(){ return new ChessPieces(this.chessInfo) } // 悔棋 repentanceChess(chess){ this.chessInfo = chess.getTallyBook() } } const chessRule = new ChessRule() const record = new Record() chessRule.playChess({ type:'黑棋', location:'X10,Y10' }) record.recordTallyBook(chessRule.getChess())//记录棋路 chessRule.playChess({ type:'白棋', location:'X11,Y10' }) record.recordTallyBook(chessRule.getChess())//记录棋路 chessRule.playChess({ type:'黑棋', location:'X11,Y11' }) record.recordTallyBook(chessRule.getChess())//记录棋路 chessRule.playChess({ type:'白棋', location:'X12,Y10' }) console.log(chessRule.getChess())//{type:'白棋',location:'X12,Y10'} chessRule.repentanceChess(record) // 悔棋 console.log(chessRule.getChess())//{type:'黑棋',location:'X11,Y11'} chessRule.repentanceChess(record) // 悔棋 console.log(chessRule.getChess())//{type:'白棋',location:'X11,Y10'}21.桥接模式
桥接模式是指将抽象部分与它的实现部分分离,使它们各自独立的变化,通过使用组合关系代替继承关系,降低抽象和实现两个可变维度的耦合度。优点
抽象和实现的分离。优秀的扩展能力。实现细节对客户透明。缺点
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。例子
比如我们所用的手机,苹果的iphoneX,和华为的mate40,品牌和型号就是它们共同的抽象部分,可以把他们单独提取出来。 class Phone { constructor(brand,modle){ this.brand = brand this.modle = modle } showPhone(){ return `手机的品牌:${this.brand.getBrand()},型号${this.modle.getModle()}` } } class Brand { constructor(brandName){ this.brandName = brandName } getBrand(){ return this.brandName } } class Modle { constructor(modleName){ this.modleName = modleName } getModle(){ return this.modleName } } const phone = new Phone(new Brand('华为'),new Modle('mate 40')) console.log(phone.showPhone())22.访问者模式
访问者模式是将数据的操作和数据的结构进行分离,对数据中各元素的操作封装独立的类,使其在不改变数据结构情况下扩展新的数据。优点
符合单一职责原则。具有优秀的扩展性和灵活性。缺点
违反了依赖倒置原则,依赖了具体类,没有依赖抽象。例子
class Phone { accept() { throw new Error('子类的accept必须被重写') } } class Mata40Pro extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return phoneVisitor.visit(this) } } class IPhone13 extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return phoneVisitor.visit(this) } } // 访问者类 class PhoneVisitor { visit(phone) { if (phone.constructor === IPhone13) { return { os: 'ios', chip: 'A15仿生芯片', screen: '电容屏' } } else if (phone.constructor === Mata40Pro) { return { os: 'HarmonyOS', chip: 'Kirin 9000', GPUType: 'Mali-G78', port: 'type-c' } } } } const mata40Pro = new Mata40Pro() console.log(mata40Pro.accept())23.解释器模式
解释器模式提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口该接口,该接口解释一个特定的上下文。优点
可扩展性比较好,灵活。增加了新的解释表达式的方式。缺点
可利用场景比较少,在web开发中几乎不可见。对于复杂的环境比较难维护。解释器模式会引起类膨胀。它还采用递归调用方法,没控制好可能会导致崩溃。例子
class TerminalExpression { constructor(data) { this.data = data } interpret(context) { if (context.indexOf(this.data) > -1) { return true; } return false; } } class OrExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.expr1.interpret(context) || this.expr2.interpret(context); } } class AndExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.expr1.interpret(context) && this.expr2.interpret(context); } } class InterpreterPatternDemo { static getMaleExpression() { const robert = new TerminalExpression("小明"); const john = new TerminalExpression("小龙"); return new OrExpression(robert, john); } static getMarriedWomanExpression() { const julie = new TerminalExpression("张三"); const married = new TerminalExpression("小红"); return new AndExpression(julie, married); } static init(args) { const isMale = this.getMaleExpression(); const isMarriedWoman = this.getMarriedWomanExpression(); console.log(`小龙是男性?${isMale.interpret("小龙")}`) console.log(`小红是一个已婚妇女?${isMarriedWoman.interpret("小红 张三")}`) } } InterpreterPatternDemo.init()总结
以上是我历时将近一个月的学习总结,然而一个月的时间是远远不够的,在写完这篇文章后,依旧对某些设计模式的应用场景缺乏了解。设计模式是要长时间深入研究的知识点,需要结合实际的场景去练习模仿,不断的去思考。另外由于js的特性,很多设计模式在js中是残缺的,非完全体的,强行用js去模仿传统的设计模式显的有点鸡肋。但是随着typescript的出现,设计模式在ts中可以无限接近传统的设计模式,后续计划写一篇ts版本的设计模式博客。本片文章学习来源:书籍《JavaScript设计模式与开发实践》双越老师的:《Javascript 设计模式系统讲解与应用》以及百度搜索借鉴的各种案例以上就是javascript的23种设计模式总结大全的详细内容,更多关于javascript设计模式的资料请关注我们其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。