这是一个新的javascript工厂模式吗?

时间:2016-04-29 17:05:49

标签: javascript design-patterns

公平的警告 - 很久以前我写了很多C ++并且无法帮助将javascript强制转换成我当时熟悉的设计模式。在任何回复中都可以指责我的atavism; - )

在我当前的项目中,我想按名称创建对象,这表示工厂模式。因此,我阅读了谷歌点击首页的javascript工厂模式'。他们都有这个丑陋的共同点:

if (name === 'FactoryPartA') {
    parentClass = PartA;
} else if (name === 'FactoryPartB') {
    parentClass = PartB;
} else if ...
    parentClass = PartZ;
}

return new parentClass();

哪个有两个问题:

  1. 每次我为工厂创建一个新零件时,我都要编辑工厂的实施,我宁愿避免工作和工作。错误插入的机会。
  2. 这是一个硬编码的线性搜索,几乎不会尖叫和效率!"
  3. 所以这就是我想出的 - 模块和工厂模式的组合,以及工厂内部定义工厂零件类别的策略所提供的模块模式的信息隐藏优点。 register关闭。

    最后回答我的问题:我不能相信以前没有比我更好的编码人员做过这样的事情,所以如果你知道的话,请在工厂模式上分享这个扭曲的规范版本的链接一个。

    N.B。在这个例子中,我将所有代码一起运行。在我的项目中,工厂,FactoryPartA,FactoryPartB和客户端代码都在单独的文件中。

    namespace('mynamespace');
    
    // object factory
    mynamespace.factory = (function () {
        'use strict';
        var api = {};
        var registry = [];
    
        // register an item
        api.register = function (item) {
            if (registry.some (function (r) {return r.name === item.name;})) { 
                throw new Error ('factory.register(): name collision detected: ' + name);
            } else {
                registry.push(item);
            }
        };
    
        // make an item given its name
        api.make = function (name) {
            var item = null;
            var idx = registry.findIndex (function (r) {
                return r.name === name;
            });
            if (idx >= 0) {
                item = new registry[idx].make();
            }
            return item;
        };
    
        return api;
    
    })();
    
    
    // define a module & register it with factory
    mynamespace.factory.register ({
        name: 'FactoryPartA',
        make: function FactoryPartA () {
            'use strict';
            var label = 'Factory Part A';   // private property
    
            this.test = undefined;  // public property
    
            this.label = function () {   // public method
                return label;
            };
    
            return this;
        }
    });
    
    // define a different module & register it with factory
    mynamespace.factory.register ({
        name: 'FactoryPartB',
        make: function FactoryPartB () {
            'use strict';
            var label = 'Factory Part B';
    
            this.test = undefined;
    
            this.label = function () {
                return label;
            };
    
            return this;
        }
    });
    
    // client code
    var aPart = mynamespace.factory.make('FactoryPartA');
    var bPart = mynamespace.factory.make('FactoryPartB');
    
    console.log (aPart.label()); // logs 'Factory Part A'
    console.log (bPart.label()); // logs 'Factory Part B'
    
    var anotherPart = mynamespace.factory.make('FactoryPartA');
    aPart.test = 'this one is not';
    anotherPart.test = 'the same as this one';
    console.log (aPart.test !== anotherPart.test); // logs true
    

1 个答案:

答案 0 :(得分:5)

回答基本问题 - 这是一个新的Javascript工厂模式 - 不,它不是。 (查看ES6/ES2015Typescript和Aurelia的依赖注入模块。)

现在,回答整体声明 - 您实际上在做的是尝试在Javascript中将元数据添加到“类”类型。问题是,看起来你正试图创建一个工厂工厂 - 我不确定你需要它。 (也许你已经简化了你的例子。)

举个例子,我会做更多这样的事情:

namespace('mynamespace');

// object factory
mynamespace.factory = (function () {
    'use strict';
    var api = {};
    var registry = {};

    // register an item
    api.register = function (name, item, overwrite) {
        if (!overwrite || registry.hasOwnProperty('name')) { 
            throw new Error ('factory.register(): name collision detected: ' + name);
        }

        registry[name] = item;
    };

    // make an item given its name
    api.make = function (name) {
        var item = registry[name];
        return item ? new item() : null; // or better, Object.create(item);
    };

    return api;

})();


// define a module & register it with factory
mynamespace.factory.register ('FactoryPartA', function FactoryPartA () {
    'use strict';
    var label = 'Factory Part A';   // private property

    this.test = undefined;  // public property

    this.label = function () {   // public method
        return label;
    };
});

// define a different module & register it with factory
mynamespace.factory.register ('FactoryPartB', function FactoryPartB () {
    'use strict';
    var label = 'Factory Part B';

    this.test = undefined;

    this.label = function () {
        return label;
    };
});

这会删除额外的工厂内容,因此它不是FactoryFactory。 (不知道为什么会这样。)此外,您提到了线性搜索 - 通过使用对象而不是数组,您可以避免线性查找(对象哈希是常量时间查找)。最后 - 您实际上可以注册任何名称,而无需将其放入包装器对象。这更接近DI。

但是,如果您确实想要采用元数据式方法,则可以执行以下操作:

namespace('mynamespace');

// object factory
mynamespace.factory = (function () {
    'use strict';
    var api = {};
    var registry = {};

    // register an item
    api.register = function (item, overwrite) {
        if (!overwrite || registry.hasOwnProperty(item.name)) { 
            throw new Error ('factory.register(): name collision detected: ' + item.name);
        }

        registry[item.name] = item;
    };

    // make an item given its name
    api.make = function (name) {
        var item = registry[name];
        return item ? new item() : null; // or better, Object.create(item);
    };

    return api;

})();


// define a module & register it with factory
mynamespace.factory.register (function FactoryPartA () {
    'use strict';
    var label = 'Factory Part A';   // private property

    this.test = undefined;  // public property

    this.label = function () {   // public method
        return label;
    };
});

// define a different module & register it with factory
mynamespace.factory.register (function FactoryPartB () {
    'use strict';
    var label = 'Factory Part B';

    this.test = undefined;

    this.label = function () {
        return label;
    };
});

这使用函数名而不是单独的属性。 (函数可以在Javascript中命名或匿名。此方法不适用于匿名函数。)我之前提到过Typescript,因为当Typescript编译为Javascript时,它实际上将很多自己的元数据放在对象上,类似于你正在做的事情。我提到了Aurelia的依赖注入,因为它的@autoinject功能实际上读取了这个元数据来创建对象,类似于你正在做的事情。

但是真的....除非你试图创建一个依赖注入容器(这需要比你的例子更多的逻辑 - 你想要一个带有可以指向实例的键的容器)我会认为你可以从Object.create()Object.assign()获得比使用这种模式更多的功能。在静态类型,强类型编译语言中,您需要的许多设计模式在该环境之外是不必要的。所以你可以这样做:

function PartA () {
    'use strict';
    var label = 'Factory Part A';   // private property

    this.test = undefined;  // public property

    this.label = function () {   // public method
        return label;
}

function PartB () {
    'use strict';
    var label = 'Factory Part B';

    this.test = undefined;

    this.label = function () {
        return label;
    };
}

var A = Object.create(PartA);
var B = Object.create(PartB);

这简单易行。