本文共 6730 字,大约阅读时间需要 22 分钟。
《设计模式:可复用的面向对象软件基础》的作者『四人帮』将设计模式宽泛地划分为以下几类:
这几类设计模式又有更详细的分类:
创建型模式:
结构型模式:
行为型模式:
而针对于 JavaScript,则有更为特殊的设计模式。
命名空间能够减少程序创建的全局变量的数量,有助于避免命名冲突或过多的名称前缀。
命名空间的思路是为应用程序或库创建一个全局对象,将所有的其他对象和函数全部添加到该对象中去,而不是去污染全局作用域。
我们可以创建单个全局对象,然后将所有的函数和对象作为该全局对象的一部分:
var FACTORY = FACTORY || {};FACTORY.Car = function () {};FACTORY.Bike = function () {};FACTORY.engines = 1;
全局命名空间对象名习惯上全部都是大写。
命名空间模式既可以限制全局变量,又能够为代码添加命名空间,看起来似乎是一个不错的方法,但多少有点繁琐,你得在每个变量和函数前面加上命名空间前缀,需要输入更多的内容,代码也变得啰嗦。另外,单个全局实例意味着任何代码都能够修改该全局实例,进而影响到其他功能。
模块模式有助于维持代码清晰的独立性以及条理性。
模块可以将较大的程序分割成较小的部分,赋予其各自的命名空间。这一点非常重要,因为一旦将代码划分成模块,这些模块就能够在其他地方重新使用。精心设计的模块接口能够使代码易于重用和扩展。
JavaScript 提供了灵活的函数和对象,可以轻松地创建出健壮的模块系统。函数作用域有助于创建模块内部使用的命名空间,对象可以用来保存导出的值。
模块可以模拟类的概念,它使得我们能够在对象中加入公共/私有方法和变量,但最重要的是,模块能够将它们同全局作用域隔离开。变量和函数都被限制在了模块作用域中,因而也就自动避免了与使用相同名称的其他脚本产生命名冲突。
模块模式的另一个有点在于它只暴露了公共 API,其他所有与内部实现相关的细节都以私有状态保留在模块的闭包中。
以下是一种模块模式的实现方法:
var revealingExample = function() { var privateOne = 1; function privateFn() { console.log('privateFn called'); } var publicTwo = 2; function publicFn() { publicFnTwo(); } function publicFnTwo() { privateFn(); } function getCurrentState() { return 2; } // 通过分配共有指针来暴露私有变量 return { setup: publicFn, count: publicTwo, increaseCount: publicFnTwo, current: getCurrentState() };}();console.log(revealingExample.current); //2revealingExample.setup(); //调用 privateFn
ES6 模块的语法类似于 CommonJS,另外还支持异步装载以及可配置的模块装载:
// json_processor.jsfunction processJSON(url) { ...}export function getSiteContent(url) { return processJSON(url);}// main.jsimport { getSiteContent } from "json_processor.js";content = getSiteContent("http://google.com/")
ES6 的导出功能可以让你使用类似于 CommonJS 的方式导出函数或变量。
工厂模式是另一种流行的对象创建模式。该模式提供了一个用于创建对象的接口。根据传入工厂的类型,可以创建出特定类型的对象。这种模式常见的实现通常是利用类或类的静态方法。这样的类或方法的目的如下:
让我们用一个常见的例子来理解工厂模式的用法。假设我们有:
CarFactory()
make()
的静态方法,该方法知道如何创建 car 类型的对象CarFactory.SUV
、CarFactory.Sedan
等我们希望像下面这样使用 CarFactory:
var golf = CarFactory.make('Compact');var vento = CarFactory.make('Sedan');var touareg = CarFactory.make('SUV');
这里给出了这种工厂的实现方法。下面的实现非常标准。我们用编程的方式调用了构造函数,创建指定类型的对象——CarFactory[const].prototype = new CarFactory();
我们将对象类型映射为构造函数。该模式的实现方法不止一种:
// 工厂构造函数function CarFactory() {}CarFactory.prototype.info = function() { console.log("This car has " + this.doors + " doors and a " + this.engine_capacity + " liter engine");};// 静态工厂方法CarFactory.make = function (type) { var constr = type; var car; CarFactory[constr].prototype = new CarFactory(); // 创建新的实例 car = new CarFactory[constr](); return car;};CarFactory.Compact = function () { this.doors = 4; this.engine_capacity = 2;};CarFactory.Sedan = function () { this.door = 2; this.engine_capacity = 2;}CarFactory.SUV = function () { this.door = 4; this.engine_capacity = 6;}var golf = CarFactory.make('Compact');var vento = CarFactory.make('Sedan');var touareg = CarFactory.make('SUV');golf.info(); // "This car has 4 door and a 2 liter engine"
mixin 模式能够显著减少代码中重复出现的功能,有助于功能重用。我们可以将能够共享的功能放到 mixin 中,以此降低共享行为的重复数量。
考虑下面的例子,我们想创建一个定制的日志记录器(logger),任何对象实例都可以使用,这个日志记录器会在希望使用/扩展 mixin 对象之间共享:
var _ = require('underscore');// 将共享的功能封装进 CustomLoggervar logger = (function () { var CustomLogger = { log: function (message) { console.log(message); } }; return CustomLogger}())// 需要定制的日志记录器来记录系统特定日志的对象var Server = (function (Logger) { var CustomServer = function () { this.init = function () { this.log("Initializing Server..."); }; }; // 将 CustomLogger 的成员复制/扩展为 CustomServer _.extend(CustomServer.prototype, Logger); return CustomServer;}(logger));(new Server()).init(); //初始化服务器
我们动态地将 mixin 的功能加入到了对象中,重要的是理解 mixin 和继承之间的区别。如果有可以在多个对象和类层次之间共享的功能,可以使用 mixin;如果要共享的功能是在单个层次中,可以使用继承。在原型继承中,如果继承来自原型,那么对原型做出的修改会影响到从原型继承的一切内容。如果不希望出现这种情况,可以使用 mixin。
在《设计模式》一书中,是这样定义观察者模式的:
对目标状态感兴趣的一个或多个观察者,通过将自身与该目标关联在一起的形式进行注册。当目标出现观察者可能感兴趣的变化时,发出提醒消息,进而调用每个观察者的更新方法。如果观察者对目标状态不再感兴趣,只需要解除关联即可。
在观察者模式中,目标保存了一个对其依赖的对象列表(称为观察者),并在自身状态发生变化时通知这些观察者。目标所采用的通知方式是广播。观察者如果不想再被提醒,可以把自己从列表中移除。我们可以对该模式中的参与者做出如下定义:
让我们来创建一个能够添加、删除和提醒观察者的目标:
var Subject = (function () { function Subject () { this.observer_list = []; } // 该方法用于向内部列表中添加观察者 Subject.prototype.add_observer = function (obj) { console.log('Added observer'); this.observer_list.push(obj); }; Subject.prototype.remove_observer = function (obj) { for(var i = 0; i < this.observer_list.length; i++) { if(this.observer_list[i] === obj) { this.observer_list.splice(i, 1); console.log('Removed Observer'); } } }; Subject.prototype.notify = function () { var args = Array.prototype.slice.call(arguments, 0); for(var i = 0; i < this.observer_list.length; i++) { this.observer_list[i].update(args) } }; return Subject})();
Subject 的实现方法非常直观,notify()
方法的重要之处在于调用所有观察者对象的 update()
方法来广播更新。
现在来定义一个能够创建随机推文的简单对象,该对象提供了一个接口,可以使用 addObserver()
和 removeObserver()
方法来添加和删除观察者,它也可以使用新获取的推文来调用 Subject 的 notify()
方法。如果出现这种情况,所有的观察者都会将新发布的推文作为参数,发出有推文更新的广播:
function Tweeter () { var subject = new Subject(); this.addObserver = function (observer) { subject.add_observer(observer); }; this.removeObserver = function (observer) { subject.remove_observer(observer); }; this.fetchTweets = function fetchTweets () { // tweet var tweet = { tweet: "This is one nice Observer" }; // 提示观察者发生的变化 subject.notify(tweet); };}
添加两名观察者:
var TweetUpdater = { update: function () { console.log('Update Tweet - ', arguments); }};var TweetFollower = { update: function () { console.log('Following this tweet - ', arguments); }};
两名观察者都有 update()
方法,该方法将由 Subject.notify()
调用。现在我们就可以通过 Tweeter 的接口将观察者添加到 Subject 中了:
var tweetApp = new Tweeter();tweetApp.addObserver(TweeterUpdater);tweetApp.addObserver(TweeterFollower);tweetApp.fetchTweets();tweetApp.removeObserver(TweetUpdater);tweetApp.removeObserver(TweetFollower);
在大型应用的构建中,我们发现某些问题模式会反复地出现,这类问题都有明确的应对方法,可以拿来重用以构建一套健壮的解决方案,这便是应用设计模式的意义所在。
原文发布时间为:2018年6月9日
原文作者:alloween.top
本文来源:如需转载请联系原作者