最近开始学习JavaScript的设计模式。首先接触到的是简单工厂Simple Factory,有点小小的感悟。归结起来就是:明明可以 new A(),为什么偏偏要 createObject("A") 呢?
关于简单工厂这种设计模式,网上有很多文章讲了实现思路。用JavaScript可以实现如下:
/** 版本1:普通实现 ************************ */ (function v1() { // 具体类A function A() { } // 具体类B function B() { } // 对象创建函数(工厂函数) function createObject(tag) { switch (tag) { case "A": return new A(); case "B": return new B(); default: return {}; } } // 客户代码 ----------------------------------- var a = createObject("A"); assert(a instanceof A); var b = createObject("B"); assert(b instanceof B); })();
问题来了:这里的代码明明可以 new A(),为什么偏偏要 createObject("A") 呢?
我认为答案在于,A、B这些类可能只存在于库代码中,并没有向客户代码开放,所以客户代码根本就不能访问到这些类。
为什么不开放呢?降低耦合度。在完成相同功能的条件下,客户代码知道得越少越好。
于是,库代码和客户代码的这种隔离,导致只能采用间接的办法来创建库中类的实例,导出一个创建函数createObject() 就是一种很简单的途径。
如下代码所示,库代码由变量library封装,而客户代码在client()中:
/** 版本2:库代码与用户代码分离 ************************ */ (function v2() { // 库代码 var library = (function () { // 具体类A function A() { } // 具体类B function B() { } // 对象创建函数(工厂函数) function createObject(tag) { switch (tag) { case "A": return new A(); case "B": return new B(); default: return {}; } } return {createObject: createObject}; })(); // 用户代码 ----------------------------------- (function client() { var a = library.createObject("A");
assert(utils.typeof(a) === "A"); var b = library.createObject("B"); assert(utils.typeof(b) === "B"); })(); })();
注意,客户代码只能使用库library导出的标识符createObject(),再也不能直接使用具体类的标识符A、B了。A、B已经变成了内部类。
通过引入一层间接性,把标记耦合(类A、B)降为数据耦合(参数"A"、"B"),既有效地隐藏了库代码的具体实现,又使得客户代码可以创建库中具体类的实例。
示例中用到的工具函数:
/** * Return the type of o as a string: * -If o is null, return "null", if o is NaN, return "nan". * -If typeof returns a value other than "object" return that value. * (Note that some implementations identify regexps as functions.) * -If the class of o is anything other than "Object", return that. * -If o has a constructor and that constructor has a name, return it. * -Otherwise, just return "Object". **/ function type(o) { var t, c, n; // type, class, name // Special case for the null value: if (o === null) return "null"; // Another special case: NaN is the only value not equal to itself: if (o !== o) return "nan"; // Use typeof for any value other than "object". // This identifies any primitive value and also functions. if ((t = typeof o) !== "object") return t; // Return the class of the object unless it is "Object". // This will identify most native objects. if ((c = classof(o)) !== "Object") return c; // Return the object's constructor name, if it has one if (o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName())) return n; // We can't determine a more specific type, so return "Object" return "Object"; } // Return the class of an object. function classof(o) { return Object.prototype.toString.call(o).slice(8, -1); } function assert(predicate) { if (predicate) { try { throw new Error(); } catch (e) { var loc = e.stack.replace(/Error\n/).split(/\n/)[1].replace(/^\s+|\s+$/, ""); console.info("assert passed:" + loc); } } else { try { throw new Error(); } catch (e) { console.log("Stack:" + e.stack); loc = e.stack.replace(/Error\n/).split(/\n/)[1].replace(/^\s+|\s+$/, ""); console.error("assert failed:" + loc); } } }