最近开始学习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);
        }
    }
}
View Code