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

is this a new javascript factory pattern?

本文关键字:javascript 工厂 模式 一个      更新时间:2023-09-26

公平的警告-很久以前,我写了很多C++,忍不住想把javascript强行引入我当时熟悉的设计模式中。在任何回复中指责我返祖是可以的;-)


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

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

它有两个问题:

  1. 每次我为工厂创建一个新零件时,我都必须编辑工厂的实现,我更喜欢避免这两项工作;错误插入机会
  2. 这是一个硬编码的线性搜索,几乎没有"效率!"

这就是我所想到的——模块和工厂模式的组合,以及通过在工厂的register闭包中定义工厂零件类的策略提供的模块模式的信息隐藏优点。

最后谈到我的问题:我不敢相信以前没有比我更好的程序员做到过这一点,所以如果你知道的话,请分享一个链接,链接到这个工厂模式的规范版本。

注意:在这个例子中,我把所有的代码都放在一起运行。在我的项目中,工厂、FactoryPartA、FactoryPart B和客户端代码都在单独的文件中。

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

要回答一个基本问题——这是一个新的Javascript工厂模式吗——不,不是。(查看ES6/ES2015、Typescript和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);

简单地说,这要容易得多。