简介:可靠的设计模式是可维护软件的基石,如果你曾参与技术面试,很有可能被问到关于设计模式的这类东西。下面这个指南中,我们将学习一些今天就可以用得着的设计模式。
什么是设计模式?
简单来说,设计模式就是对特定类型问题重用的软件解决方案,这些问题在软件开发的时候经常会碰到,通过很多年的实践,专家对一些相似地问题总结出一些方法,这些方法就封装成为一种设计模式,所以:
模式是一种经验证的用于解决软件开发问题的方案。
模式是可扩展的,因为他们经常被结构化而且你需要遵循某些规则。
对于相似的问题,模式可被重用
在接下来的教程中,我们将直接给出一些设计模式的例子。
设计模式的种类
软件开发中,设计模式通常分为几种类别,在这篇教程中我们重点介绍以下三种:
1、创建型模式专注于构建对象或者类,对象的创建听起来很简单(在某些情况下),但是大型应用需要控制对象的创建过程。
2、结构型设计模式专注于管理对象之间的关系使得应用是用一种可扩展的架,,结构型模式关键点是确保在应用程序中部分改变不会影响其他部分。
3、行为模式专注于对象间的通信
你可能在读完这些简介的描述后仍然觉得有问题,这很正常,一旦我们看完了这些模式的深入介绍后,问题也会变得明朗起来,所以接着往下看吧。
类在JavaScript中的注意点:
当我们读设计模式时,你经常会提及到类和对象。这很疑惑,因为JavaScript没有真正“类”的构造,一个更合适的术语叫“数据类型”。
JavaScript中的数据类型:
JavaScript是一门面向对象的语言,一个对象继承自其他对象,这个概念以原型继承著称。一个数据类型可以通过构造函数创建,就像:
- function Person(config) {
- this.name = config.name;
- this.age = config.age;
- }
- Person.prototype.getAge = function() {
- return this.age;
- };
- var tilo = new Person({name:"Tilo", age:23 });
- console.log(tilo.getAge());
当方法定义在Person数据类型中时注意prototype的使用,由于多个Person对象将引用同一个prototype,这样就允许 getAge()方法可以被所有的Person数据类型的实例共享。而不是每个实例都重新定义一次,除此之外,任何继承自Person的数据类型都可以访问getAge()方法。
#p#
处理私有数据
在JavaScript中另一个常见的问题是没有真正意义上的私有变量,然而我们可以使用闭包
去模拟私有变量,考虑下面这代码片段:
- var retinaMacbook = (function() {
- //Private variables
- var RAM, addRAM;
- RAM = 4;
- //Private method
- addRAM = function (additionalRAM) {
- RAM += additionalRAM;
- };
- return {
- //Public variables and methods
- USB: undefined,
- insertUSB: function (device) {
- this.USB = device;
- },
- removeUSB: function () {
- var device = this.USB;
- this.USB = undefined;
- return device;
- }
- };
- })();
在上面这个例子中,我们创建了一个retinaMacbook对象,含有公有和私有变量及方法,可以这样来使用它:
- retinaMacbook.insertUSB("myUSB");
- console.log(retinaMacbook.USB); //logs out "myUSB"
- console.log(retinaMacbook.RAM) //logs out undefined
在JavaScript中函数和闭包可以做更多的事,但是我们在这个教程中没法涉及到方方面面,我们简短的学习了JavaScript的数据类型和私有变量。现在我们可以学习设计模式了。
创建型设计模式:
有很多种不同的创建设计模式,但是在这里我们主要讨论两种,建造模式(Builder)和原型模式(Prototype)。
建造模式:
建造模式通常用于web开发,有时你在使用它你却还没意识到。简而言之,这个模式可以定义如下:
“使用建造模式允许我们仅仅通过指定类型和内容来构造一个对象,我们不需要明确的创建对象。”
例如,你可能无数次的使用jQuery:
- var myDiv = $('<div id="myDiv">This is a div.</div>');
- //myDiv now represents a jQuery object referencing a DOM node.
- var someText = $('<p/>');
- //someText is a jQuery object referencing an HTMLParagraphElement
- var input = $('<input />');
看看上面这三个例子,第一个,传递了一个<div/>元素附带一些内容,第二个,传递一个空的<p>标签,第三个,传递一个<input/>元素。这三个例子的结果都是一样的:返回一个jQuery对象的引用指向一个DOM节点。
在jQuery中$变量采用的就是建造模式,例如:返回的jQuery Dom对象可以访问由jQuery库提供的所有方法,但是并没有显示的调用document.createElement,JS 库通常都是通过这种高级方法处理的。
想象有多少工作要做,如果我们显示创建DOM元素然后插入内容到里面。通过利用建造模式,我们可以专注于对象的类型和内容,而不是显示的去创建。
#p#
原型模式
之前,我们讨论了在JavaScript中通过函数和添加方法到对象的原型中定义一个数据类型。原型模式通过原型允许对象继承自其它对象。
“原型模式是一个基于已经存在的模板对象克隆出新对象的模式”
在JavaScript中这是一种简单自然的方式来实现继承。例如:
- var Person = {
- numFeet: 2,
- numHeads: 1,
- numHands:2
- };
- //Object.create takes its first argument and applies it to the prototype of your new object.
- var tilo = Object.create(Person);
- console.log(tilo.numHeads); //outputs 1
- tilo.numHeads = 2;
- console.log(tilo.numHeads) //outputs 2
属性(方法)在Person对象中应用到了tilo对象的原型,我们可以重新定义在tilo对象中的属性,如果我们想要它不一样的话。上面例子中,我们使用Object.create(),然而,IE8中不支持这个比较新的方法,在这种情况下,我们可以模拟他的行为:
- var vehiclePrototype = {
- init: function (carModel) {
- this.model = carModel;
- },
- getModel: function () {
- console.log( "The model of this vehicle is " + this.model);
- }
- };
- function vehicle (model) {
- function F() {};
- F.prototype = vehiclePrototype;
- var f = new F();
- f.init(model);
- return f;
- }
- var car = vehicle("Ford Escort");
- car.getModel();
唯一不好的地方就是这个方法你没法指定为可读的属性,而使用Object.create()时可以指定。不管怎样,原型模式展示了对象如何继承自其它对象。
结构化模式:
结构化设计模式在当你想理解一个系统如果工作的时候显得非常有帮助。它能使应用扩展方便,维护方便。我们将讨论以下两种模式:组合模式和门面模式
组合模式:
组合模式可以理解为一个对象的组合可以像单个对象一样以一致的方式处理。这是什么意思呢?好吧,考虑下面这个例子:
- $('.myList').addClass('selected');
- $('#myItem').addClass('selected');
- //dont do this on large tables, it's just an example.
- $("#dataTable tbody tr").on("click", function(event){
- alert($(this).text());
- });
- $('#myButton').on("click", function(event) {
- alert("Clicked.");
- });
很多JavaScript库提供了一致的API,不论是处理单个DOM元素还是一个DOM元素的数组。我们可以添加selected 类给所有含.myList的选择器的元素。同样我们可以使用相同的方法处理相似的DOM元素#myItem,类似的,我们可以使用on()方法附上事件处理器在多个节点或单个节点上。
#p#
门面模式:
隐藏内部复杂结构,提供给用户简单接口使用。
门面模式几乎总是可以改善大部分软件的可用性。使用jQuery作为例子,一个最受欢迎的方法ready():
- $(document).ready(function() {
- //all your code goes here...
- });
ready()方法就实现的门面模式,如果你去查看源代码,你将发现:
- ready: (function() {
- ...
- //Mozilla, Opera, and Webkit
- if (document.addEventListener) {
- document.addEventListener("DOMContentLoaded", idempotent_fn, false);
- ...
- }
- //IE event model
- else if (document.attachEvent) {
- // ensure firing before onload; maybe late but safe also for iframes
- document.attachEvent("onreadystatechange", idempotent_fn);
- // A fallback to window.onload, that will always work
- window.attachEvent("onload", idempotent_fn);
- ...
- }
- })
ready()方法并不简单,jQuery规范游览器的一致性确保ready()在合适的时间被触发。然而,作为一名开发者,你应该用简单的接口展示出来。
总结:设计模式最让人鼓舞的是有人在过去已经成功实践了。很多开源代码实现了各种JavaScript中的是设计模式。作为程序员,我们需要意识到每种设计模式的应用场景。我希望这篇教程能帮助一步步回答这些问题。
英文原文:Tilo Mitra