理解设计模式(一)——简单工厂

理解工厂模式(一)

写在前面

最近在上设计模式这门课,吐槽一下某人教的实在是,听不懂(不过比上学期教的好多了,值得鼓励一下)。于是自己上网搜索资料,然后看书,稍微有点体会。

说了点废话,下面开始。

为什么要用工厂模式

显然,要用工厂模式是因为我们遇到了一些问题。那么我们到底遇到了什么问题,才要使用工厂模式呢?下面直接用伪代码举个例子来说明一下。

// BlaBlaBla 假设这里是一些其他的代码
// ...
let logger1 = new FileLogger();
logger1.log();
// BlaBlaBla 假设这里是一些其他的代码
// ...
let logger2 = new FileLogger();
logger2.log();

这段代码假设了这样一个场景,我们需要在一个程序中的多个不同的地方使用Logger,用来记录日志。这里我们使用了FileLogger。

这段代码有什么问题?某人大概会说,没有面向抽象编程,而是面向了具体编程。啥玩意儿,说人话。

举例来说,如果我们要把日志记录到标准I/O流里面,而不是File里面,我们需要如何修改程序?把代码里面所有的FileLogger替换成IOLogger?这样做,我们需要把分散在代码当中各处的FileLogger一个一个找出来,然后替换,实在是有点麻烦。

还有,如果我们要允许用户自己选择使用哪种Logger,现在这样的代码结构只能使用if…else进行判断,如果要增加一种情况,就要增加一个if…具体如下所示:

if(userChoose == 'file') {
let logger = new FileLogger();
}
if(userChoose == 'db') {
let logger = new DBLogger();
}
// ...如果要添加一种Logger...
// 就要添加一个if语句

这是完全无法接受的。

解决方案

那么我们如何解决这个问题呢?简单工厂模式就是来做这个事情的。下面直接用代码举例子:

class Factory {
function create(loggerType) {
if(loggerType == 'file') {
return new FileLogger();
}
if(loggerType == 'db') {
return new DBLogger();
}
}
}

// ...BlaBlaBla
// 假设这里是其他代码
let factory = new Factory();
let logger1 = factory.create('file');
logger1.log();
// ...BlaBlaBla
// 假设这里是其他代码
let logger2 = factory.create('db');
logger2.log();

等等!是不是感觉和直接new没有什么区别?甚至还多此一举加了一个Factory,白白增加代码量?是的,这就是很多例子的问题。这样子并没有发挥简单工厂模式的作用。

回到那个让用户自己选择Logger类型的例子,真正使用简单工厂模式应该是这样的:

class Factory {
function create(loggerType) {
if(loggerType == 'file') {
return new FileLogger();
}
if(loggerType == 'db') {
return new DBLogger();
}
}
}

//读取配置文件———划重点
let config = readConfig();

// ...BlaBlaBla
// 假设这里是其他代码
let factory = new Factory();
let logger1 = factory.create(config.type);
logger1.log();
// ...BlaBlaBla
// 假设这里是其他代码
let logger2 = factory.create(config.type);
logger2.log();

现在再想想,用户如果要替换另一个类型的Logger,是不是只要修改配置文件就可以了?不用在代码中到处找散落在各处的new语句了?

也就是说,我们增强了代码的稳定性,通俗点说,就是不用到处改代码,只要改配置文件。本质上来说,这就是增加了代码的内聚。也就是把原本散落在各处的new语句聚集到了一起。

思考

那么,我们就必须要声明一个Factory类来做这个事情吗?或者说,我们必须要使用面向对象的思想来解决这个问题吗?其实不是的。

想一想,我们最根本的目的是什么?是为了实现能只修改一个地方,就能修改整个代码中多个地方创建的对象的类型。这也就是所谓的对象懒创建(Lazy create)。意思是,等到我运行到创建的那一行语句的时候,再决定创建什么对象。而new语句是在编译的时候就决定了创建什么对象。

用例子来说明,new LoggerFile()factory.create(config.type)的区别就是,new语句从一开始就决定了我要创建一个LoggerFile类型的对象,而factory等到运行到这一行时,才根据config.type的值来决定创建什么类型的对象。这就是Lazy的地方。

好了,有点扯出去了。下面给出Javascript不使用面向对象,解决我们上面提到的问题的代码:

// 读取config
let config = readConfig();
// 我就不喜欢Factory这个名字,我就要叫它god
let god = {};

// 告诉god如何创建一个FileLogger
god['file'] = () => new FileLogger();
// 告诉god如何创建一个DBLogger
god['db'] = () => new DBLogger();

// 要修改logger1的类型,只要修改config文件
let logger1 = god[config.type]();
logger1.log();

这里,没有用到面向对象,同样解决了上面的问题。当然这有Javascript语言特性的原因在里面。而且我可以说,这样的代码的优越性是要高于之前面向对象的代码的。

之前面向对象的代码,我们如果要新增一种Logger类型,就要在工厂中新加一个if语句。而我现在给出的代码,不需要对已有代码进行修改,只需要在后面添加就行。如果你愿意,也可以把新添加的代码单独开一个文件。这部分如何实现可以自己思考一下。

总结

简单工厂就是为了解决这样一个问题:如果要修改创建的对象的类型,就要在整个程序中修改所有的new语句。使用了简单工厂就可以实现改变一个地方,就能改变整个程序中创建的所有对象的类型。

当然这个方法还是有问题。就像上面说的,如果你非得用面向对象的方法,你现在要在工厂里新增加一个Logger类型,就得增加一个if语句。

谁来解决这个问题呢?麻烦的工厂方法模式可以解决这个问题。但是我真的不喜欢,太麻烦了。但是,要上课,要考试鸭。现在这里挖个坑,下一篇就来讲讲工厂方法模式吧。

JS天下第一。

希望某些人能提高一下自己的职业技能,业务能力。

Author: LeoB_O
Link: https://leob-o.github.io/2019/04/10/FactoryPattern1/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.