前言:
设计模式不容易用文字描述清楚,而过多的代码,看起来也让人摸不到头脑,加上词语或者文字描述的抽象感,很容易让人看了无数设计模式的文章,也仍然理解不了。 所以我一直打算写此系列博客,首先我会从大量文章里去理解这些设计模式,最后我用自己的语言组织转化为博客,希望用更少的代码,更容易理解的文字,来聊一聊这些设计模式。 我所理解、所描述的每一个设计模式也可能有些是错误的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客内容。 因为我是前端,所以设计模式的代码以前端代码和视角为主。 此博客内容对每一种模式并不会写得非常深入,也许能为读者打通一些认知,如果看了此系列博客,再去看其他更深入的博客,可能是一种比较好的方式。
单例模式
单例是保证一个类只创建一个实例,实例不存在新建一个实例,实例存在返回已经存在的实例。 单例模式很好理解,使用情况也很多,比如我最近做的ng4的项目,会定义一些service(ng的service都是单例),service里面存放的数据供全局使用,所有组件共享这个service。 再说一个应用实例: 全局toast框,我们只需要每次调用同一个实例改变toast框里的文本,并控制其隐藏显示。
工厂模式
下面是书中工厂模式的例子,关于它的实现和优劣我就不说了,这里只是以个人的理解来说为什么它叫工厂模式。 创建对象的时候,都会先新建一个新的原生对象,再对它进行属性赋值,最后返回一个成品对象。所有属性和方法都在它内部,都是它唯一拥有的。那么传统工厂生产出来的电视手机等这些硬件,他们就是这种模式,他们的在出厂前就会在产品内部定义好所有的组件,最后加工为成品。所以我们就这样来理解工厂模式的命名吧。
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
建造者模式
使用多个简单的对象一步一步构建成一个复杂的对象。它使组装过程和每个部件的开发分离开来。
直接举个例子: 假如我们要设计一个h5飞机游戏,那么我们设计一款飞机的前端代码,把飞机先简单拆分为如下两个部件: 机体、子弹。每个部件定义一个对象,然后设置它们的参数(比如飞机图片地址,子弹设置伤害100的威力值和子弹图片地址),最后通过组装代码逻辑来组装成一架完整的游戏飞机。 那么飞机是一个复杂对象,这两个部件就是更单纯更简单的对象。来点代码示例:
function plane(){ this.buildBodyModule();
this.buildBulletModule(); } plane.prototype = { buildBodyModule: function(){ this.body = {
imgUrl:'xxx.png',
destoryImgUrl: 'xx2.png'
} }, buildBulletModule: function(){ this.bullet = {
imgUrl: 'xx3.png',
power: 100
} } }
当需求变动的时候,我们只需修改对应的单个部件,甚至可以随时在移除或者添加其它部件。 所以这样我们就大致能理解为什么它叫建造者模式了,现实生活中,一个复杂工程,就比如汽车、建筑、飞机等,他们都是由不同的精细设计的部件通过合理的组装才生产出成品的。 建造者模式和装饰者模式看起来实际有点类似,下面就说装饰者模式。
装饰者模式
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 其实装饰者模式就是通过一个特定的方法,给一个实例对象添加特定的功能。比如一个基础汽车类,有引擎、轮胎和车架,代码如下:
function BaseCar(){} BaseCar.prototype = { engine: e300, tyre: t200, frame: f10 }
我们在车的基础上,贴一些装饰纸,以下代码就是最基础的装饰者模式的使用。
var car = new BaseCar(); setStyle(car); setStyle(carObj){ carObj.style = '达康书记玻璃贴纸'; }
所谓装饰者模式,就是通过一个外部的方法,来包装一个对象,并且扩展它。
装饰者模式 VS 建造者模式
实际装饰者模式和建造者模式,都有类似分步的方式去建立一个对象。而建造者模式是在类的内部,一步步建造对象,很强调整体性和逻辑性,当然我上面的例子并没有体现逻辑性之说,如果我们把飞机的机体再拆分一下,我们可能需要先建立机体的机舱(图片),再根据机舱位置,放置或者说定位侧翼和尾翼图片位置,那么这就是所谓逻辑性了。 而装饰者模式,是传入对象到外部函数,通过函数体内部对对象进行扩展。其实通过外部函数,也能达到逻辑性的要求,通过外部函数的多次调用,让对象添加和迭代功能。但是如果所有的组装过程都是通过外部函数进行,在对象属性和子对象的引用上可能会让代码变得非常复杂,甚至可能需要函数里返回对象,再用另一个函数包裹这类的函数嵌套,整体的逻辑从代码层面看来也会变得复杂和难以理解。 对于装饰者模式的使用,还是应该如它的名字一样,就应该是为一个基础功能完整的对象添加一些额外的扩展。 在面向对象编程的程序里,建造者模式和装饰者模式应该是常会结合在一起使用,把上面的汽车例子,改编成一个更像这两模式结合的代码:
function BaseCar(){ this.buildFrame(); this.buildEngine(); this.buildTyre(); } BaseCar.prototype = { buildFrame: function(){ this.frame = 'f10'; //todo sth }, buildEngine: function(){ this.engine = 'e300'; //todo sth }, buildTyre: function(){ this.tyre = 't200'; //todo sth } } var car = new BaseCar(); setStyle(car); setStyle(carObj){ carObj.style = '达康书记玻璃贴纸'; }
外观模式 门面模式
看了这个图实际还不能彻底对它进行定性,当我再看到下面的代码示例的时候,我就彻底清楚了外观模式的定义:
function SubSystemOne(){ //xxx } function SubSystemTwo(){ //xxx } function SubSystemThree(){ //xxx } facade(){ SubSystemOne(); SubSystemTwo(); SubSystemThree(); }
外观模式是把一系列逻辑封装到一个方法中,使当前逻辑更易使用,更易维护。那么我们实际前端开发中,会常常用到,说一个前端开发中的例子: 一个后台管理系统,有一个用户列表页面,添加用户和修改用户信息,触发添加或者修改按钮的时候,都是使用的同一个弹窗模板,然后根据不同的传参去判断是否是新增或者修改,再去改变弹框模板表单里的数据,添加的时候所有表单为空,编辑的时候把之前数据载入到表单。那么这个弹窗模板就是门面、也就是上图的Facade。表单的html代码是不变的,没有必要写两个模板,所以这也可以叫代码去重。
再举一个例子: 上面说的弹框模板的表单,在新增用户成功的回调函数里我们需要把表单里的数据给重置了,代码如下:
addUser(){ //获取form数据, addModel 设置参数 addModel(function(){ //添加成功回调 this.nodes.$form.address.val(''); this.nodes.$form.cell.val(''); this.nodes.$form.name.val(''); }) }
这时编辑成功后也需要去执行上面回调里的重置代码,我平时为了代码去重,就需要写一个单独的重置方法,在两个回调里调用,代码如下:
addUser(){ //获取form数据, addModel 设置参数 addModel(function(){ //添加成功回调 reset() }) } editUser(){ //获取form数据, editModel 设置参数 editModel(function(){ //添加成功回调 reset() }) } function reset(){ this.nodes.$form.address.val(''); this.nodes.$form.cell.val(''); this.nodes.$form.name.val(''); }
reset方法就是Facade,甚至说有更复杂的需求的时候,reset方法还可以传参数,根据参数来重置某几个表单,总之reset方法把重置表单的逻辑封装在此方法内,其他地方需要调用重置相关功能,都用经过它才行,这样可以减少代码的耦合性,去除很多重复代码,后期维护也非常清晰、改动也方便。 JQ的 $ 选择器,其实也是此模式,它专门处理DOM选择,集合了id、class等等选择器,我们只需要在传参数的时候前面加上#或者.,$('#id'),$('.class'),就能选择相应的dom。
结语:
其实设计模式并不是很神秘,很牛逼冲天的技巧,也许你在不经意间写出的代码就是一种模式,这些设计模式只是针对一些写法做了定义和命名。 这是此系列博客的第一篇。我不知道自己描述出来的设计模式,是否能被广大同行所接受或者让人能看明白。 如果你觉得此博客对你有帮助,欢迎留言或者点击推荐,更多的反馈和支持可能是我坚持写下去的理由和动力!