赞
踩
设计模式的原则是找出程序中的变化,并将变化封装起来,实现高效的可复用性。核心在于意图,而不在结构。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性。我们使用设计模式的最终目的是为了实现代码的高类聚和低耦合。你是否思考过这样的一个问题,如何让代码写的更有健壮性,其实核心在于把握变与不变。确保变的部分更加灵活,不变的地方更加稳定,而使用设计模式可以让我们达到这样的目的。
1.S – Single Responsibility Principle 单一职责原则
a.一个程序只做好一件事
b.如果功能过于复杂就拆分开,每个部分保持独立
2.O – OpenClosed Principle 开放/封闭原则
a.对扩展开放,对修改封闭
b.增加需求时,扩展新代码,而非修改已有代码
3.L – Liskov Substitution Principle 里氏替换原则
a.子类能覆盖父类
b.父类能出现的地方子类就能出现
4.I – Interface Segregation Principle 接口隔离原则
a.保持接口的单一独立
b.类似单一职责原则,这里更关注接口
5.D – Dependency Inversion Principle 依赖倒转原则
a.面向接口编程,依赖于抽象而不依赖于具体
b.使用方只关注接口而不关注具体类的实现
设计模式可以分为三大类:
1.结构型模式(Structural Patterns): 通过识别系统中组件间的简单关系来简化系统的设计。常见结构性模式如下:
a.适配器模式
b.装饰器模式
c.代理模式
d.外观模式
e.桥接模式
f.组合模式
g.享元模式
2.创建型模式(Creational Patterns): 处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。常见创建型模式如下:
a.单例模式
b.原型模式
c.工厂模式
d.抽象工厂模式
e.建造者模式
3.行为型模式(Behavioral Patterns): 用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。常见行为型模式如下:
a.观察者模式
b.迭代器模式
c.策略模式
d.模板方法模式
e.职责链模式
f.命令模式
g.备忘录模式
h.状态模式
i.访问者模式
j.中介者模式
k.解释器模式
下面我们选择一些在前端开发过程中常见的模式进行一一讲解。
1.单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
场景:确保只有一个实例。比如:登录浮窗、Vue中的axios实例(我们对axios进行请求拦截和响应拦截,多次调用封装好的axios但是仅设置一次,封装好的axios导出就是一个单例)、全局状态管理 store等
示例:
// 单例构造器 const FooServiceSingleton = (function () { // 隐藏的Class的构造函数 function FooService() {} // 未初始化的单例对象 let fooService; return { // 创建/获取单例对象的函数 getInstance: function () { if (!fooService) { fooService = new FooService(); } return fooService; } } })(); const fooService1 = FooServiceSingleton.getInstance(); const fooService2 = FooServiceSingleton.getInstance(); console.log(fooService1 === fooService2); // true
优点:
划分命名空间,减少全局变量
增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护且只会实例化一次。简化了代码的调试和维护
缺点:
不适用动态扩展对象。
2.策略模式
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换
适用场景:根据表单验证策略进行表单验证等
示例:
<!DOCTYPE html> <html> <head> <title>策略模式-校验表单</title> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> </head> <body> <form id = "registerForm" method="post" action="http://xxxx.com/api/register"> 用户名:<input type="text" name="userName"> 密码:<input type="text" name="password"> 手机号码:<input type="text" name="phoneNumber"> <button type="submit">提交</button> </form> <script type="text/javascript"> // 策略对象 const strategies = { isNoEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, isNoSpace: function (value, errorMsg) { if (value.trim() === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.trim().length < length) { return errorMsg; } }, maxLength: function (value, length, errorMsg) { if (value.length > length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) { return errorMsg; } } } // 验证类 class Validator { constructor() { this.cache = [] } add(dom, rules) { for(let i = 0, rule; rule = rules[i++];) { let strategyAry = rule.strategy.split(':') let errorMsg = rule.errorMsg this.cache.push(() => { let strategy = strategyAry.shift() strategyAry.unshift(dom.value) strategyAry.push(errorMsg) return strategies[strategy].apply(dom, strategyAry) }) } } start() { for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) { let errorMsg = validatorFunc() if (errorMsg) { return errorMsg } } } } // 调用代码 let registerForm = document.getElementById('registerForm') let validataFunc = function() { let validator = new Validator() validator.add(registerForm.userName, [{ strategy: 'isNoEmpty', errorMsg: '用户名不可为空' }, { strategy: 'isNoSpace', errorMsg: '不允许以空白字符命名' }, { strategy: 'minLength:2', errorMsg: '用户名长度不能小于2位' }]) validator.add(registerForm.password, [ { strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' }]) validator.add(registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: '请输入正确的手机号码格式' }]) return validator.start() } registerForm.onsubmit = function() { let errorMsg = validataFunc() if (errorMsg) { alert(errorMsg) return false } } </script> </body> </html>
优点
利用组合、委托、多态等技术和思想,可以有效的避免多重条件选择语句
提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,理解,易于扩展
利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的代替方案
缺点
会在程序中增加许多策略类或者策略对象
要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy
3.工厂模式
定义:工厂模式是指通过一个工厂类来创建其他类的实例。这个模式非常适合那些需要根据不同条件创建不同实例的场景
场景:如jquery的$函数,该函数接收一个选择器字符串作为参数,然后返回与该选择器匹配的一个或多个元素。
(function () { function jQuery(selector) { return new jQuery.prototype.init(selector); } // jQuery对象的构造函数 jQuery.prototype.init = function (selector) { } // jQuery原型上的css方法 jQuery.prototype.css = function (config) { } // 将jQuery原型上的方法都放到init的原型链上 jQuery.prototype.init.prototype = jQuery.prototype; window.$ = window.jQuery = jQuery; })();
示例:
function Factory (name, age) {
this.name = name;
this.age = age;
}
Factory.prototype.say = function () {
console.log(我的名字叫${this.name}, 我今年${this.age}了
)
}
let zs = new Factory(‘张三’, 18);
let ls = new Factory(‘李四’, 20);
zs.say()
ls.say()
优点
创建对象的过程可能很复杂,但我们只需要关心创建结果。
构造函数和创建者分离, 符合“开闭原则”
一个调用者想创建一个对象,只要知道其名称就可以了。
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
缺点
添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度
4.外观模式
定义:外观模式是最常见的设计模式之一,它为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。简而言之外观设计模式就是把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API
场景:比如JQuery就把复杂的原生DOM操作进行了抽象和封装,并消除了浏览器之间的兼容问题,从而提供了一个更高级更易用的版本。
示例:
//兼容浏览器事件绑定
let addMyEvent = function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false)
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn)
} else {
el['on' + ev] = fn
}
};
优点
减少系统相互依赖。
提高灵活性。
提高了安全性
缺点
不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
5.代理模式
定义:代理模式是指使用一个代理对象来控制对另一个对象的访问。访问之前先做一层拦截,这个模式非常适合那些需要控制对某些敏感资源的访问的场景。
场景:如缓存接口请求,如vue3的响应式原理proxy
示例:
let obj1 = { name:'vue', age:'10' } let obj2 = new Proxy(obj1,{ get(target,property) { //访问obj2的某个属性的时候调用 //target即obj这个对象 //property 所访问的属性 console.log('触发get'); return target[property]; }, set(target,property,newVal) { //修改obj2的某个属性的时候调用 //target即obj这个对象 //property 所访问的属性 //newVal 要设置的新值 console.log('触发set') target[property] = newVal; }, }) obj2.age = '12'; console.log(obj2.age);
优点
代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则;
缺点
处理请求速度可能有差别,非直接访问存在开销
6.观察者模式
定义:观察者模式又称发布订阅模式(Publish/Subscribe Pattern),是我们经常接触到的设计模式,日常生活中的应用也比比皆是,比如你订阅了某个博主的频道,当有内容更新时会收到推送;又比如JavaScript中的事件订阅响应机制。观察者模式的思想用一句话描述就是:被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者。
场景:比如给DOM元素绑定事件的 addEventListener() 方法、还有Vue中的watch 就是依赖于这个模式,在wacth中,被监听的数据充当了发布者,而执行的回调函数充当了订阅者。当数据变化时,发布者会通知所有订阅者执行回调函数,从而实现对数据变化的监听。
示例:
document.body.addEventListener("click", function() {
alert("Hello World");
});
document.body.click();
在这里需要监控用户点击 document.body 的动作,但是我们没办法预知用户将在什么时候点击。所以我们订阅了 document.body 的 click 事件,当 body 节点被点击时,body 节点便会向订阅者发布这个消息
优点
支持简单的广播通信,自动通知所有已经订阅过的对象
目标对象与观察者之间的抽象耦合关系能单独扩展以及重用
增加了灵活性
观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。
缺点
过度使用会导致对象与对象之间的联系弱化,会导致程序难以跟踪维护和理解
7.适配器模式
定义:适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。
场景:统一多个类的接口设计、兼容老版本接口、适配不同格式的数据等
示例:
class GooleMap { show() { console.log('渲染地图') } } class BaiduMap { display() { console.log('渲染地图') } } class GaodeMap { show() { console.log('渲染地图') } } // 上面三个类,如果我们用多态的思想去开发的话是很难受的,所以我们通过适配器来统一接口 class BaiduAdaapterMap { show() { return new BaiduMap().display() } }
优点
可以让任何两个没有关联的类一起运行
提高了类的复用
增加了类的透明度
灵活性好
缺点
过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
8.装饰器模式
定义:装饰器模式是一种在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责的设计模式。在前端开发中,装饰者模式可以用于对已有功能的扩展和增强。
场景:ES7中增加了对装饰器Decorator的支持,它就是用来修改类的行为,或者增强类,这使得在js中使用装饰者模式变得更便捷。
示例:
// 原始对象 function Component() {} Component.prototype = { operation: function () { console.log("Component operation"); }, }; // 装饰者对象 function Decorator(component) { this.component = component; } Decorator.prototype = { operation: function () { this.component.operation(); console.log("Decorator operation"); }, }; // 使用 var component = new Component(); component.operation(); // 输出 "Component operation" var decorator = new Decorator(component); decorator.operation(); // 输出 "Component operation" 和 "Decorator operation"
优点
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果
装饰器模式完全遵守开闭原则
缺点
装饰器模式会增加许多子类,过度使用会增加程序得复杂性
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。