子慕谈设计模式系列(一)

351 阅读8分钟

前言:

设计模式不容易用文字描述清楚,而过多的代码,看起来也让人摸不到头脑,加上词语或者文字描述的抽象感,很容易让人看了无数设计模式的文章,也仍然理解不了。 所以我一直打算写此系列博客,首先我会从大量文章里去理解这些设计模式,最后我用自己的语言组织转化为博客,希望用更少的代码,更容易理解的文字,来聊一聊这些设计模式。 我所理解、所描述的每一个设计模式也可能有些是错误的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客内容。 因为我是前端,所以设计模式的代码以前端代码和视角为主。 此博客内容对每一种模式并不会写得非常深入,也许能为读者打通一些认知,如果看了此系列博客,再去看其他更深入的博客,可能是一种比较好的方式。

单例模式

单例是保证一个类只创建一个实例,实例不存在新建一个实例,实例存在返回已经存在的实例。 单例模式很好理解,使用情况也很多,比如我最近做的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 = '达康书记玻璃贴纸';
}

外观模式 门面模式

外观模式(Facade),为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使得这一子系统更加容易使用。 这是百度百科的定义描述。唉,其实这个模式很好理解,但是我一开始看到这个描述我就真的看不懂。“为接口提供一个界面”,这是什么描述,作为一个前端,界面就会理解成页面或者肉眼能看到的界面。为接口提供一个界面是什么gui。。 好吧,再看下结构图:

看了这个图实际还不能彻底对它进行定性,当我再看到下面的代码示例的时候,我就彻底清楚了外观模式的定义:

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。

结语:

其实设计模式并不是很神秘,很牛逼冲天的技巧,也许你在不经意间写出的代码就是一种模式,这些设计模式只是针对一些写法做了定义和命名。 这是此系列博客的第一篇。我不知道自己描述出来的设计模式,是否能被广大同行所接受或者让人能看明白。 如果你觉得此博客对你有帮助,欢迎留言或者点击推荐,更多的反馈和支持可能是我坚持写下去的理由和动力!

此系列博客目录:

子慕谈设计模式系列(一)
子慕谈设计模式系列(二)——设计模式六大原则
子慕谈设计模式系列(三)