设计模式
设计模式,其实就是一种写代码时的设计思想。
分类:
-
创建型
-
单例模式
-
原型模式
-
构造器模式
-
工厂模式
-
抽象工厂模式
-
-
结构型
-
桥接模式
-
外观模式
-
组合模式
-
装饰器模式
-
适配器模式
-
代理模式
-
享元模式
-
-
行为型
-
迭代器模式
-
解释器模式
-
观察者模式
-
中介者模式
-
访问者模式
-
状态模式
-
策略模式
-
命令模式
-
模板模式
-
为什么要学习理解设计模式
我们在写代码,写页面时,为了可维护、可迭代、可读性、低耦合性等等,有时需要考虑 逻辑代码该怎么封装,组件该怎么封装,设计模式 就是 前人从以往的经验中总结出来的一些 书写代码的思想(或者说套路),帮助我们更快的做决定、做判断,为了更好的应对我们的场景,我们眼下这块代码该怎么写。
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结(来自百度百科)
注意:
-
我们实现某块功能,某个组件时,可以结合多个设计模式
外观模式(Facade)
概念:
-
为子系统中的 一组接口 提供一个一致的界面,外观模式定义了 一个高层接口
这个 接口 使得这一子系统更加容易使用。
引入外观角色之后,用户只需要直接与外观角色交互
用户与子系统之间的复杂关系由外观角色内部来处理,从而降低了系统的耦合度
应用示例:
普通登录、邮箱登录、手机登录,三个表单,那么我们就要判断该显示那个表单
先经过一层简单封装,变为:
逻辑处理还是留在了使用层
所以,使用 外观模式:
将逻辑判断和处理留在封装组件的内部,组件变得可读,可随意抽离
单例模式
概念:
-
保证一个类只有一个实例(提供一个访问它的全局访问点,无论”创建“多少次,都只返回第一次所创建的那个唯一的实例)
-
单例模式是创建型设计模式的一种。针对全局仅需一个实例的场景
实现需求:
-
具备判断自己是否已经创建过该实例的的能力
理解与应用
-
jquery 我们全局需要几个,只需要一个,所以这就是符合单例模式的
-
逻辑代码层面:
-
静态方法实现:
<span style="background-color:#f8f8f8"> <span style="color:#aa5500">// 静态方法的实现</span> <span style="color:#770088">class</span> <span style="color:#0000ff">SingleLoading</span> { <span style="color:#000000">show</span> () { <span style="color:#000000">console</span>.<span style="color:#000000">log</span>(<span style="color:#aa1111">'这是一个单例Loading'</span>) } <span style="color:#770088">static</span> <span style="color:#000000">getInstance</span>(){ <span style="color:#aa5500">// 判断是否已经创建过实例</span> <span style="color:#770088">if</span> (<span style="color:#981a1a">!</span><span style="color:#000000">SingleLoading</span>.<span style="color:#000000">instance</span>) { <span style="color:#aa5500">// 将创建的实例对象保持下来</span> <span style="color:#000000">SingleLoading</span>.<span style="color:#000000">instance</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">SingleLoading</span>() } <span style="color:#770088">return</span> <span style="color:#000000">SingleLoading</span>.<span style="color:#000000">instance</span> } } <span style="color:#770088">const</span> <span style="color:#0000ff">loading1</span> <span style="color:#981a1a">=</span> <span style="color:#000000">SingleLoading</span>.<span style="color:#000000">getInstance</span>() <span style="color:#770088">const</span> <span style="color:#0000ff">loading2</span> <span style="color:#981a1a">=</span> <span style="color:#000000">SingleLoading</span>.<span style="color:#000000">getInstance</span>() <span style="color:#000000">console</span>.<span style="color:#000000">log</span>(<span style="color:#000000">loading1</span> <span style="color:#981a1a">===</span> <span style="color:#000000">loading2</span>) <span style="color:#aa5500">// true</span></span>
-
闭包实现
<span style="background-color:#f8f8f8"> <span style="color:#770088">class</span> <span style="color:#0000ff">SingleLoading</span> { <span style="color:#000000">show</span> () { <span style="color:#000000">console</span>.<span style="color:#000000">log</span>(<span style="color:#aa1111">'这是一个单例Loading'</span>) } } <span style="color:#000000">SingleLoading</span>.<span style="color:#000000">getInstance</span> <span style="color:#981a1a">=</span> (<span style="color:#770088">function</span>(){ <span style="color:#aa5500">// 定义自由变量instance,模拟私有变量</span> <span style="color:#770088">let</span> <span style="color:#0000ff">instance</span> <span style="color:#981a1a">=</span> <span style="color:#221199">null</span> <span style="color:#770088">return</span> <span style="color:#770088">function</span>(){ <span style="color:#770088">if</span>(<span style="color:#981a1a">!</span><span style="color:#0055aa">instance</span>) { <span style="color:#aa5500">// 如果为null则new出唯一实例</span> <span style="color:#0055aa">instance</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">SingleLoading</span>() } <span style="color:#770088">return</span> <span style="color:#0055aa">instance</span> } })(); <span style="color:#770088">const</span> <span style="color:#0000ff">loading3</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">SingleLoading</span>().<span style="color:#000000">getInstance</span>() <span style="color:#770088">const</span> <span style="color:#0000ff">loading4</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">SingleLoading</span>().<span style="color:#000000">getInstance</span>() <span style="color:#000000">console</span>.<span style="color:#000000">log</span>(<span style="color:#000000">loading3</span> <span style="color:#981a1a">===</span> <span style="color:#000000">loading4</span>)</span>
-
-
封装组件层面:
我们页面中有时需要显示弹框,而且同时只能显示一个(也就是说只需要一个),那么就可以用单例模式的思想来封装一个 弹框组件
实现的核心思想:是否已创建弹框,没有则创建,有则直接使用
这样做的好处:避免了频繁创建和销毁实例,减少内存消耗
工厂模式
分类:
-
简单工厂模式
、工厂方法模式
、抽象工厂模式
概念:
-
简单工厂模式
又叫静态工厂模式
,由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。(白话:只要我能做,要什么样的,做什么样的)
理解与应用:
-
工厂生产零件,我们常用的(需要重复用)的细小零件都可以采用工厂模式来生产,工厂会根据我们的要求来生产零件。
-
在组件封装层面:例如一些按钮,输入框,我们在很多地方要使用,但是每次使用这些小零件的时候,可能存在细微的差别,那么,我们就要将我们的建造的那个工厂,尽可能打造成一个尽可能灵活的工厂,让其可以做各种型号的螺丝
展示一个很好的 输入框的工厂组件 案例
-
在逻辑代码封装层面:例如 不同的管理人员,有不同的权限,也可以用简单工厂模式来整理
<span style="background-color:#f8f8f8"> <span style="color:#aa5500">//User类</span> <span style="color:#770088">class</span> <span style="color:#0000ff">User</span> { <span style="color:#aa5500">//构造器</span> <span style="color:#000000">constructor</span>(<span style="color:#0000ff">opt</span>) { <span style="color:#770088">this</span>.<span style="color:#000000">name</span> <span style="color:#981a1a">=</span> <span style="color:#0055aa">opt</span>.<span style="color:#000000">name</span>; <span style="color:#770088">this</span>.<span style="color:#000000">viewPage</span> <span style="color:#981a1a">=</span> <span style="color:#0055aa">opt</span>.<span style="color:#000000">viewPage</span>; } <span style="color:#aa5500">//静态方法</span> <span style="color:#770088">static</span> <span style="color:#000000">getInstance</span>(<span style="color:#0000ff">role</span>) { <span style="color:#770088">switch</span> (<span style="color:#0055aa">role</span>) { <span style="color:#770088">case</span> <span style="color:#aa1111">'superAdmin'</span>: <span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">User</span>({ <span style="color:#000000">name</span>: <span style="color:#aa1111">'超级管理员'</span>, <span style="color:#000000">viewPage</span>: [<span style="color:#aa1111">'首页'</span>, <span style="color:#aa1111">'通讯录'</span>, <span style="color:#aa1111">'发现页'</span>, <span style="color:#aa1111">'应用数据'</span>, <span style="color:#aa1111">'权限管理'</span>] }); <span style="color:#770088">break</span>; <span style="color:#770088">case</span> <span style="color:#aa1111">'admin'</span>: <span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">User</span>({ <span style="color:#000000">name</span>: <span style="color:#aa1111">'管理员'</span>, <span style="color:#000000">viewPage</span>: [<span style="color:#aa1111">'首页'</span>, <span style="color:#aa1111">'通讯录'</span>, <span style="color:#aa1111">'发现页'</span>, <span style="color:#aa1111">'应用数据'</span>] }); <span style="color:#770088">break</span>; <span style="color:#770088">case</span> <span style="color:#aa1111">'user'</span>: <span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">User</span>({ <span style="color:#000000">name</span>: <span style="color:#aa1111">'普通用户'</span>, <span style="color:#000000">viewPage</span>: [<span style="color:#aa1111">'首页'</span>, <span style="color:#aa1111">'通讯录'</span>, <span style="color:#aa1111">'发现页'</span>] }); <span style="color:#770088">break</span>; <span style="color:#770088">default</span>: <span style="color:#770088">throw</span> <span style="color:#770088">new</span> <span style="color:#000000">Error</span>(<span style="color:#aa1111">'参数错误, 可选参数:superAdmin、admin、user'</span>) } } }</span>
状态模式
概念:
-
行为基于它的状态改变而改变。属于行为型模式
-
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态改变而改变的context对象
-
每个阶段的状态都是固定好的
理解与应用:
-
实现tab切换时,我们用
curActive
来标识当前状态,封装好的组件就是一个“context”,这就是一种状态模式的应用 -
实现状态进度条时,也是状态模式的一种应用
完善的状态模式
-
真正完善的状态模式,还需要提供状态随意切换的功能,提供各种切换状态方式(好比浏览器的前进后退)。
策略模式
扩展性极强
概念:
-
一个类的行为或其算法,可以在运行时更改。属于行为型模式
-
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法
理解与应用:
-
状态模式和策略模式很相似,但是策略模式的内部处理可以是外部使用时再决定如何处理,状态模式都是已经准备好的。
-
状态模式:程序运行过程中不同状态之间可以随意转换
-
策略模式:选择一种策略执行一次
-
-
策略模式说简单一点,就是要对一些事情做处理,但是 处理方式 <--- 由使用策略时选择的策略决定。也就是,实现不同东西,使用不同策略
-
在组件封装层面:
封装全局提示框时:
可以先封装3种效果及功能的"策略",然后给外界提供一个 "context"组件,context负责组织和按需调用我们的策略代码。
也就是,我们的提示框内容,以及提示框按钮的功能都要封装成:使用时传递这些内容和功能
模板模式
概念:
-
定义一个操作中的算法的骨架,但是将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
理解与应用:
-
封装不变的部分,扩展可变的部分
-
例如我们渲染列表时,都要经过获取数据、显示正在加载、获取到数据后就渲染列表、是否没有更多数据的状态,这些固定的部分就可以封装成一个骨架,我们每次渲染列表时,都用这个骨架,及不必每次都声明一个
data、loading、noMore
等等变量来每次实现一遍这种固定套路。 -
其实
vue
、react
宏观上就是模板模式,各种响应式,各种依赖,简化我们从Model层到View层要做的很多事情
享元模式
概念:
-
主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
-
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
理解应用:
-
JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
-
数据库的数据池。
-
优点:大大减少对象的创建,降低系统的内存,使效率提高。
观察者模式
概念:
-
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
-
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
理解应用:
-
redux
其实就是一种观察者模式,有发布、订阅等等 -
vue2
的源码中其实就应用了观察者模式,收集依赖,同时在这些依赖的角度,就是在订阅这个数据,当数据变化时,观察者 触发视图更新。
适配器模式
概念:
-
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
理解应用:
-
将一个已有的类的接口,转化成客户希望的另外一个接口(方法或属性),让原本由于接口不兼容而不能一起工作的那些类可以正常协作,简单理解就是为了兼容而生的“转换器”
-
逻辑代码封装层面:
当我们为我们的项目封装 请求接口
request[type](url, data, config)
方法时,其实就可以应用适配器模式因为在使用 get 请求时,我们要传的是 params
而在使用 post 请求时,我们要传的是 data
但是我们为了使用时方便简洁,我们在封装时,request 的第二个参数
data
就要采用适配器思想,不管是要传params
还是data
都 从第二个参数传对象即可。之后,原本是一个get请求格式,一个post请求格式,就很好的兼容为一个 request 请求格式
-
提高了我们封装出的功能的灵活性
装饰者模式
概念:
-
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰,来包裹真实的对象
-
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能
-
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
理解与应用:
-
TS下就有装饰器
-
在Java中,可以用来降低子类的膨胀
-
封装组件层面:
我们在使用某些组件时,当前这个组件的功能不能满足我们的需求,例如 input,我们很多input都需要带有校验功能
这时,我们就可以封装一个装饰器思想的组件,这个组件给传入的Input提供校验功能,antDesign、elementUI等 校验表单就提供了类似的包裹性组件
<span style="background-color:#f8f8f8"> <span style="color:#117700"><</span><span style="color:#117700">valid-input</span> <span style="color:#0000cc">field</span>=<span style="color:#aa1111">"username"</span> <span style="color:#0000cc">options</span>=<span style="color:#aa1111">"[{ rule: required, message: '用户名必须' }]"</span><span style="color:#117700">></span> <span style="color:#117700"><</span><span style="color:#117700">input</span> <span style="color:#0000cc">v-model</span>=<span style="color:#aa1111">"username"</span> <span style="color:#0000cc">type</span>=<span style="color:#aa1111">"text"</span> <span style="color:#117700">/></span> <span style="color:#117700"></</span><span style="color:#117700">valid-input</span><span style="color:#117700">></span></span>
还有,我有次在封装一个弹窗框时,其实也涉及这个思想,就是弹窗的性质就像一种装饰器,我们想弹窗显示某个组件,就使用这个弹窗组件包裹装饰(再结合上外观模式,我们把弹窗相关的功能都封装到这个组件中,易于使用)
代理模式
概念:
-
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
-
为其他对象提供一种代理以控制对这个对象的访问
-
和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
-
和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
理解与应用:
-
Vue3
用到的proxy
-
想在访问一些内容时做一些控制,就考虑用代理模式
-
可以用来避免已有代码的过度封装,保证代码职责清晰