子慕谈设计模式系列(三)
前言
设计模式不容易用文字描述清楚,而过多的代码,看起来也让人摸不到头脑,加上词语或者文字描述的抽象感,很容易让人看了无数设计模式的文章,也仍然理解不了。 所以我一直打算写此系列博客,首先我会从大量文章里去理解这些设计模式,最后我用自己的语言组织转化为博客,希望用更少的代码,更容易理解的文字,来聊一聊这些设计模式。 我所理解、所描述的每一个设计模式也可能有些是错误的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客内容。 我作为前端开发者,所以设计模式的代码以前端代码和视角为主。 此博客内容对每一种模式并不会写得非常深入,也许能为读者打通一些认知,如果看了此系列博客,再去看其他更深入的博客,可能是一种比较好的方式。
代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
生活中的例子:买火车票不一定在火车站买,也可以去代售点或者网上购买,vpn。
常见的代理模式应用例子:前后端交互,往往后端有server层和应用层,前端只和后端应用层打交道,应用层调取server层,server层职责单一,应用层处理逻辑。 应用层既是代理层,也可以叫中间件。
代理模式最开始我一直没有想到平时前端应用中的例子。但是经过多次推敲和联系,发现代理模式的使用无处不在,再读一遍描述(在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。)。那么我认为对API的封装可以算是代理模式的应用,一般对API进行封装,肯定是希望在调用API的时候做一些通用的事情或者处理,以便让对象的调用更方便或者更适应业务。比如我们要封装一下js的location.href的跳转。我们想在跳转之前进行一些统计,那么我们可以在location.href前处理跳转逻辑。但是因为要跳转的地方太多了,我不想每个地方都有跳转逻辑,所以我封装一个jump方法,专门处理跳转,并在跳转之前处理统计逻辑。而后我在跳转前还需要做其他事情,就可以直接在这个jump方法里实现,扩展性也非常高。
function jump(url){ //统计逻辑 location.href = url; } jump('baidu.com')
优点:
- 职责清晰
- 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
- 高扩展性
缺点:
- 会使程序处理和响应速度变慢
中介者模式
中介者模式是用来降低多个对象和类之间的交互复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的交互,并支持松耦合,使代码易于维护。 中介者模式属于行为型模式。
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,形成网状结构。
典型应用例子:MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
相信现在的框架模式至少都是mvc或者mvvm。我曾经看过一个运行的老项目.net代码,一个aspx文件中写了html,css,js,.net,并且数据库查询操作也写到页面中了,那页面维护起来就非常酸爽了。 如果使用mvc模式,控制器作为中介,隔离model和view让它们不直接交互,达到了解耦效果,虽然会增加代码量,但是交互的可重用性得到了提升,代码复杂度降低,逻辑更加清晰,更易于维护,也就是所谓高内聚低耦合。
现实中的例子:在学校下发一个通知的时候,如果是通过口口相传的方式,那么学生之间的关系是多对多的关系,非常难以准确传达到每个人手里。如果学校贴一张公告单到校门口,那么公告单作为学校和学生的中介,就能让关系变得简单。我们平时生活中的聊天群也可以达到同样效果。网状结构的关系由此变成了星型结构。如下盗图:
观察者模式
观察者模式(有时又被称为发布-订阅模式)是设计模式的一种。 在此种模式中, 一个或者多个观察者去监听主题,当主题发生变化的时候,主题会通知所有的观察者。 这通常透过调用观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。 比如常用的事件监听,onclick,onload等。
vue采用数据劫持结合观察者模式,通过Object.defineProperty方法
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的回调。 写一个删减版的类似例子(直接复制代码在控制台运行试试):
function Observer(data){//传入观察对象 this.data = data; this.defineReactive(data); } Observer.prototype = { defineReactive: function(data){ var self = this; var dep = new Dep(); dep.addSub(data);//添加订阅 Object.keys(data).forEach(function(key){ var val = data[key]; Object.defineProperty(data, key, { get: function(){ return val; }, set: function(newVal){ if(val == newVal) return; val = newVal; dep.notify();//发生改变下发通知 } }) }) } } function Dep(){} Dep.prototype = { addSub: function(sub){ this.sub = sub; }, notify: function(){ console.log(this.sub);//当前对象有值的改变,打印出当前对象 } } var data = { val: 1 } new Observer(data); data.val = 2;//执行后,在控制台会打印出当前对象,直接复制这段代码运行试试
策略模式
- 定义了一系列算法;
- 封装了每个算法;
- 这一系列的算法可互换代替。
var validator = { isNumber:function(val){ if(false) alert('非法数字'); //return true or false }, isPhone:function(){} //..... }
这样在使用和维护验证的时候,重复代码量会比较多,一个验证方法需要处理验证算法和验证后的逻辑(验证返回、提醒等),不管提醒是写在验证方法内还是在外部方法外,这些逻辑每次都要处理一次。 那么使用策略类,来统一处理验证逻辑,并把验证算法独立开,这样之后维护只修改和添加算法方法。 那么怎么做呢,如下代码:
var validator = { validate: function(type, val){ for (prop in this.types) { if(type == prop){//调用对应验证方法 var result = this.types[type].validate(val); if(!result) alert(this.types[type].text); return result; } } }, types: {//定义验证算法 isNumber:{ validate: function(val){ //判断 return true or false; }, text: '非法数字' }, isPhone:function(){} //..... } } var input1 = $('input').val(); validator.validate('isNumber', input1)
以上代码只是一个小demo,并不完善,想表达的意思就是策略模式,把算法和策略类独立开来,根据需求算法可以替换,策略类统一处理。 angular的验证类FormGroup就使用了这一模式。 上面的demo因为还比较简单,所以一下感觉不到有多大差别,但是如果你真正使用过类型angular的FormGroup这类的验证方式,你就会发现能节省很多代码量,逻辑也非常清晰。
状态模式
允许一个对象在其内部状态改变时改变它的行为。 对象看起来似乎修改了它的类。通俗的说,对象内部定义了不同状态的类,在状态改变的时候,替换对象相应的类。
举一个生活中的例子:当我们的手机电量比较充足的时候,手机会使用正常模式用电,当电量小于20%的时候,手机会使用省电模式。电量就是状态,正常模式和省电模式就是相应的替换类。
再举一个前端的例子: 博客园的后台首页右上角,有两种形态,一种是登录之后,会显示当前用户名和注销按钮,一种是没有登录会展示登录按钮和注册按钮。 假如博客园使用了双向绑定, 那么在模板里,会有登录和未登录的html代码,然后通过一个指令(比如ngShow)传入控制器变量操作谁隐藏谁显示。 那么控制器的变量就是状态,登录和未登录模板代码就是状态对应的类。 这个模式我想作为前端肯定是会常常使用的。所以此模式就不用代码描述了。
此系列博客目录: