设计模式(design pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。是某种场景下解决问题的范式,因此掌握设计模式对于一个 Programmer 来说非常重要。本文介绍了几种常见的设计模式。
原型模式(The Prototype Pattern)
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var vehicle = { getModel: function ( ) { console .log( "The model of this vehicle is.." + this .model ); } };var car = Object .create(vehicle, { "id" : { value: MY_GLOBAL.nextId(), enumerable: true }, "model" : { value: "Ford" , enumerable: true } });var beget = (function ( ) { function F ( ) {} return function (proto ) { F.prototype = proto; return new F(); }; })();var car = beget(vehicle);
优点:
JavaScript 本身提供的原型优势;
所有对象实例共享原型中的方法,可以带来性能提升。
缺点:
构造器模式(The Constructor Pattern )
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Car (model, year, miles ) { this .model = model; this .year = year; this .miles = miles; } Car.prototype.toString = function ( ) { return this .model + " has done " + this .miles + " miles" ; };var civic = new Car("Honda Civic" , 2009 , 20000 );var mondeo = new Car("Ford Mondeo" , 2010 , 5000 );console .log(civic.toString());console .log(mondeo.toString());
模块模式(The Module Pattern )
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var myNamespace = (function ( ) { var myPrivateVar, myPrivateMethod; myPrivateVar = 0 ; myPrivateMethod = function (foo ) { console .log(foo); }; return { myPublicVar: "foo" , myPublicFunction: function (bar ) { myPrivateVar++; myPrivateMethod(bar); } }; })();
优点:
比真正封装的想法更加清晰;
支持私有变量、私有方法。
缺点:
无法在以后添加到对象的方法中访问私有成员;
当更改可见性时,必须对使用该成员的每个位置进行更改;
无法为私有成员创建自动单元测试;
当错误需要热修复时的额外复杂性。
揭示模块模式(The Revealing Module Pattern )
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var myRevealingModule = (function ( ) { var privateVar = "Ben Cherry" , publicVar = "Hey there!" ; function privateFunction ( ) { console .log( "Name:" + privateVar ); } function publicSetName ( strName ) { privateVar = strName; } function publicGetName ( ) { privateFunction(); } return { setName: publicSetName, greeting: publicVar, getName: publicGetName }; })(); myRevealingModule.setName( "Paul Kinlan" );
优点:
使脚本的语法更加一致;
使模块结尾处的内容更加清晰,增强了可读性;
缺点:
单例模式(The Singleton Pattern )
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 var SingletonTester = (function ( ) { function Singleton (options ) { options = options || {}; this .name = "SingletonTester" ; this .pointX = options.pointX || 6 ; this .pointY = options.pointY || 10 ; } var instance; var _static = { name: "SingletonTester" , getInstance: function (options ) { if (instance === undefined ) { instance = new Singleton(options); } return instance; } }; return _static; })();var singletonTest = SingletonTester.getInstance({ pointX: 5 });console .log( singletonTest.pointX );
优点:
提供了对唯一实例的受控访问;
相比于静态类(或对象),单例模式可以延迟初始化;
对于需要频繁创建和销毁的对象,可以避免对资源的多重占用,节约系统资源,提高效率;
缺点:
单例模式一般没有接口,扩展困难;
不适用于变化的对象。
观察者模式(The Observer Pattern )
观察者组件:
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function ObserverList ( ) { this .observerList = []; } ObserverList.prototype.add = function (obj ) { return this .observerList.push(obj); }; ObserverList.prototype.count = function ( ) { return this .observerList.length; }; ObserverList.prototype.get = function (index ) { if (index > -1 && index < this .observerList.length) { return this .observerList[index]; } }; ObserverList.prototype.indexOf = function (obj, startIndex ) { var i = startIndex; while (i < this .observerList.length) { if (this .observerList[i] === obj) { return i; } i++; } return -1 ; }; ObserverList.prototype.removeAt = function (index ) { this .observerList.splice(index, 1 ); };function Subject ( ) { this .observers = new ObserverList(); } Subject.prototype.addObserver = function (observer ) { this .observers.add(observer); }; Subject.prototype.removeObserver = function (observer ) { this .observers.removeAt(this .observers.indexOf(observer, 0 )); }; Subject.prototype.notify = function (context ) { var observerCount = this .observers.count(); for (var i=0 ; i < observerCount; i++){ this .observers.get(i).update(context); } };function Observer ( ) { this .update = function ( ) { }; }
观察者实例(Vue.js 的双向绑定实现):
{.line-numbers} 1 2 3 4 <div id ="app" > <input type ="text" name ="" > <p class ="log" > </p > </div >
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var oApp = document .getElementById('app' );var oInput = oApp.children[0 ];var oLog = oApp.children[1 ];function extend (obj, extension ) { for ( var key in extension ){ obj[key] = extension[key]; } } extend(oInput, new Subject()); oInput.oninput = function (message ) { this .notify(this .value); } extend(oLog, new Observer()); oLog.update = function (message ) { this .innerHTML = message; } oInput.addObserver(oLog);
优点:
观察者模式对于在应用程序设计中解耦许多不同的场景非常有用;
观察者模式可以在不使类紧密耦合的情况下保持相关对象之间的一致性;
观察者和主体之间可以存在动态关系,这提供了很大的灵活性。
缺点:
观察者和主体相关联,具有依赖关系,不利于应用的解耦。
发布/订阅模式(The Publish/Subscribe Pattern)
作为观察者模式的变体,其与观察者模式的不同在于:
订阅者不需要绑定发布者;
允许任何订阅者注册和接收发布者的广播。
发布订阅组件:
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var pubsub = {}; (function (myObject ) { var topics = {}; var subUid = -1 ; myObject.publish = function (topic, args ) { if (!topics[topic]) { return false ; } var subscribers = topics[topic], len = subscribers ? subscribers.length : 0 ; while (len--) { subscribers[len].func(topic, args); } return this ; }; myObject.subscribe = function (topic, func ) { if (!topics[topic]) { topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token: token, func: func }); return token; }; myObject.unsubscribe = function (token ) { for (var m in topics) { if (topics[m]) { for (var i = 0 , j = topics[m].length; i < j; i++) { if (topics[m][i].token === token) { topics[m].splice(i, 1 ); return token; } } } } return this ; }; }(pubsub));
发布订阅实例:
{.line-numbers} 1 2 3 4 5 <div id ="app" > <input type ="text" name ="" > <p class ="log" > </p > <p class ="log" > </p > </div >
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var oApp = document .getElementById('app' );var oInput = oApp.children[0 ];var oLog_1 = oApp.children[1 ];var oLog_2 = oApp.children[2 ];var subscriber = pubsub.subscribe("event_input" , function (topic, data ) { oLog_1.innerHTML = data; });var subscriber = pubsub.subscribe("event_input" , function (topic, data ) { oLog_2.innerHTML = data.split("" ).reverse().join("" ); }); oInput.oninput = function ( ) { pubsub.publish("event_input" , this .value); }
优点:
发布订阅模式有效地将应用程序分解为更小,更松散耦合的块,以改善代码管理和重用;
发布者和订阅者之间不存在依赖,一个发布者可以绑定多个不同类型的订阅者。
缺点:
由于订阅者和发布者之间的动态关系,更新依赖性可能难以跟踪,同时也不方便排查错误。
工厂模式(The Factory Pattern)
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function Car (options ) { this .doors = options.doors || 4 ; this .state = options.state || "brand new" ; this .color = options.color || "silver" ; }function Truck ( options ) { this .state = options.state || "used" ; this .wheelSize = options.wheelSize || "large" ; this .color = options.color || "blue" ; }function VehicleFactory ( ) {} VehicleFactory.prototype.vehicleClass = Car; VehicleFactory.prototype.createVehicle = function (options ) { switch (options.vehicleType){ case "car" : this .vehicleClass = Car; break ; case "truck" : this .vehicleClass = Truck; break ; } return new this .vehicleClass(options); };var carFactory = new VehicleFactory();var car = carFactory.createVehicle({vehicleType : "car" , color : "yellow" , doors : 6 });console .log(car instanceof Car); var movingTruck = carFactory.createVehicle({vehicleType : "truck" , state : "like new" , color : "red" , wheelSize : "small" });console .log( movingTruck instanceof Truck );
优点:
当对象或组件设置涉及高度复杂性时,有利于降低使用的复杂度;
可以根据所处的环境轻松生成不同的对象实例;
有利于处理许多共享相同属性的小对象或组件;
对于需要调用不同实例的对象可以提供一个统一的入口,方便解耦。
缺点:
可能给应用程序带来不必要的大量复杂性;
由于对象创建过程被抽象化,对于复杂度过大的对象,可能导致单元测试的问题。
抽象工厂模式(The Abstract Factory Pattern)
相比于工厂模式,抽象工厂将一组对象的实现细节与其一般用法分开。适用于系统必须独立于生成对象的方式,或者需要使用多种类型的对象的场景。
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var abstractVehicleFactory = (function ( ) { var types = {}; return { getVehicle: function (type, customizations ) { var Vehicle = types[type]; return (Vehicle ? new Vehicle(customizations) : null ); }, registerVehicle: function (type, Vehicle ) { var proto = Vehicle.prototype; if (proto.drive && proto.breakDown ) { types[type] = Vehicle; } return abstractVehicleFactory; } }; })(); abstractVehicleFactory.registerVehicle("car" , Car); abstractVehicleFactory.registerVehicle("truck" , Truck);var car = abstractVehicleFactory.getVehicle("car" , {color : "lime green" , state : "like new" });var truck = abstractVehicleFactory.getVehicle("truck" , {wheelSize : "medium" , color : "neon yellow" });
混入模式(The Mixin Pattern)
{.line-numbers} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 var Car = function (settings ) { this .model = settings.model || "no model provided" ; this .color = settings.color || "no colour provided" ; };var Mixin = function ( ) {}; Mixin.prototype = { driveForward: function ( ) { console .log("drive forward" ); }, driveBackward: function ( ) { console .log("drive backward" ); }, driveSideways: function ( ) { console .log("drive sideways" ); } };function augment (receivingClass, givingClass ) { if (arguments [2 ]) { for (var i = 2 , len = arguments .length; i < len; i++) { receivingClass.prototype[arguments [i]] = givingClass.prototype[arguments [i]]; } } else { for (var methodName in givingClass.prototype) { if (!Object .hasOwnProperty.call(receivingClass.prototype, methodName)) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } } } augment(Car, Mixin, "driveForward" , "driveBackward" );var myCar = new Car({model : "Ford Escort" , color : "blue" }); myCar.driveForward(); myCar.driveBackward(); augment(Car, Mixin);var mySportsCar = new Car({model : "Porsche" , color : "red" }); mySportsCar.driveSideways();
参考资料