工厂设计模式
场景示例
比如现在需要对手机目录下的文档进行读写,考虑到不同的文档类型有不同的操作,于是抽象一个Document接口来抽象文档的读写过程,不同的文档实现这个Document接口,完成具体的读写过程。
Document接口定义如下:
public Interface Document {
void read();
void save();
void edit();
String path();
}
具体的文档类型定义示例如下:
// excel文档
public class Excel implements Document {
void read() {
// 读取excel内容的逻辑
}
void save() {
// 保存excel内容的逻辑
}
void edit() {
// 编辑excel内容的逻辑
}
String path() {
return "excel file path"
}
}
// 文本文件
public class Text implements Document {
void read() {
// 读取txt文本文件的逻辑
}
void save() {
// 保存txt文件的逻辑
}
void edit() {
// 编辑txt文本文件的逻辑
}
String path() {
return "txt file path"
}
}
// ... and so on
由于文档的类型有多种,不同的文档读写方法也不尽相同,故设计了不同的子类来完成具体的读写操作。
在遍历到对应类型的文档类型时,我们需要实例化不同的子类来完成文档的读写。那么如何实例化这些子类呢?
我们可以将实例化的操作放在一个方法里,针对不同的文档类型实例化不同的子类并返回。伪代码如下:
public class DocumentFactory {
// 创建文档对象
public Document createDocument(int type) {
Document document;
if (type == TXT) {
document = new Text();
} else if (type == EXCEL) {
document = new Excel();
} else if (type == AUDIO) {
document = new Audio();
}
return document;
}
}
/**
* 遍历文件夹的伪代码
*/
public void listFiles() {
for (File f : fileList) {
int type = f.type();
Document document = DocumentFactory.createDocument(type)
document.read();
}
}
拿到对应的文档对象后,就可以直接调用读写的方法了。
这个就是简单工厂的设计模式了,createDocument就可以看成是工厂方法,根据类型生产一个个的产品(子类)。
在开发中,我们可能会遇到要创建本质相同,但表现又有所差异的对象来完成一些逻辑。(如此处的文档,但是各文档格式又不同)在设计了多个不同的子类后,为了方便的创建和获取子类,就可以采取工厂模式了。
记得在知乎有个答主用一句话描述了工厂模式的本质【工厂设计模式本质上是对获取对象过程的抽象】。个人觉得很贴切,也很好理解。为什么要对过程抽象呢?因为有些对象的创建过程并不容易,不是简单的通过new就可以创建的(此处的示例较为简略,只做说明之用)。结合此例,可以知道工厂的设计模式抽象了创建具体对象的过程,我们只提供类型,工厂方法帮我们生成对应的产品(子类对象),具体的生产过程我们不关心。
此时的工厂实现是一个具体的类,而不是接口或抽象类。如果我们需要增加子类对象的时候,我们就必须修改工厂方法,增加额外的if else判断来创建新的子类。但是这样就违背了开闭原则(设计模式中规定对功能扩展开放,对功能修改关闭,此处是直接修改了功能,故违背了开闭原则)。
对于类型不多的场景修改起来也没有太大的成本。但对于子类对象变更或新增频繁的场景,此类简单工厂设模式应付起来可能就捉襟见肘了。 那么,如何修改以便适应较多对象的创建过程呢?
在《程序员的自我修养》一书中有一句名言:
没有什么计算机问题是不能通过增加一层解决的。
我们可以通过增加一层逻辑层来处理这个问题。这个抽象的逻辑层就是工厂接口。
首先设计一个工厂接口,如下,抽象出生产产品的方法。
/**
* 文档创建接口
*/
public interface IDocumentFactory {
Document createDocument();
}
然后具体的产品生产放在具体的产品工厂中,伪代码如下:
public TxtFactory implements IDocumentFactory {
Document createDocument() {
// create txt document object
}
}
其他的子类类同。此时,创建具体产品的逻辑过程就交给了具体的产品工厂了。 之前是在一个工厂方法中创建多个子类,现在是不同的子工厂生产不同的子产品,相当于是在整个过程中增加了一层来处理逻辑了,而整个增加的一层就是子工厂类。
在需要具体的产品时就可以调用具体的工厂生产具体的产品了。
IDocumentFactory factory = new TxtFactory();
Document txt = factory.createDocument();
/**
* 遍历文件夹的伪代码
*/
public void listFiles() {
IDocumentFactory factory;
for (File f : fileList) {
int type = f.type();
if (type == TXT) {
factory = new TxtFactory()
document = factory.createDocument();
} else if (type == EXCEL) {
dofactory = new ExcelFactory()
document = factory.createDocument();
} else if (type == AUDIO) {
dofactory = new AudioFactory()
document = factory.createDocument();
}
}
}
从代码可以看出,之前是直接new对象,现在是通过工厂来创建对象,以此来屏蔽更多对象的生产细节,从而实现代码上的解耦。并且一个对象使用一个工厂来生产,这样就符合了设计模式的开闭原则了(新增产品创建时只需新增对应的工厂类即可)。
这便是工厂模式,与简单的工厂模式不同,在创建对象时使用专有的工厂类来创建。但是在产品类较多的情况下,工厂模式会大大增加类的个数(因为每个对象都需要一个对应的工厂类),一定程度上增加了系统的复杂性。
在工厂模式中,工厂方法中就不存在if else判断了,因为生产的都是具体的产品。 (但在此例中,只是将if else方法移到了业务层了。 如何在业务调用层也减少if - else的判断,可以参看其他网络博客,主要有反射、使用map、枚举等方式,此处不在赘述)。
此处示例只有简单几种产品对象,在涉及产品族(某个大类产品下,又具体细分几个子类产品)的情况下,则可以使用抽象工厂方法。
此时,需要对工厂方法进行一定的抽象。一个工厂方法可以创建同大类不同子类的对象,然后对不同的工厂抽象出相同的部分,得到顶层的抽象工厂。 此处的文档示例可能不太合适,故参考此篇博客。 在此基础上理解,感谢原作者分享。
如,创建车,车有品牌和类型属性,比如品牌有benz和telsa,类型有跑车和商务类型车,此时两两组合就有了四种类型了。
- telsa 跑车
- telsa 商务车
- benz 跑车
- benz 商务车
首先,我们抽象出车这个概念,然后衍生出telsa车和benz车,继而衍生出telsa跑车和telsa商务车,同理benz车。 伪代码如下:
// 车类接口
public interface ICar {
}
public class TelsaCar implements ICar {
}
public class BenzCar implements ICar {
}
// 再次拓展
public class TelsaSportCar extends TelsaCar {
}
public class TelsaBusinessCar extends TelsaCar {
}
public class BenzSportCar extends BenzCar {
}
public class BenzBusinessCar extends BenzCar {
}
有了这么多类型,就需要有不同的工厂类来生产这些对象或产品。抽象出如下的工厂类
public class SportFactory {
getBenzCar()
getTelsaCar()
}
public class BusinessFactory {
getBenzCar();
getTelsaCar();
}
(也可以设计TelsaFactory和BusinessFactory,原理类同)
此时,可以看到这两个工厂都有相同的方法,可以将这些相同或公共的方法,再抽象一层出来,作为顶层的抽象工厂,就可以得到更加灵活的结构。伪代码如下:
public abstract class AbstractCarFactory {
getBenzCar();
getTelsaCar();
}
// 然后将上面的工厂修改为扩展该抽象工厂类。
通过抽象工厂,定义生产不同产品大类的方法,不同的子类产品再通过对应的具体工厂来生产产品。
抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
分析
从上面的分析过程来看,工厂模式其实是一个不停的对创建对象过程或逻辑进行包装的过程,每包装一层代码逻辑在一定程度上进行了解耦,比如以前要访问对象A,并且对象A在版本不断迭代的过程中接口或逻辑会不断变化,那么访问对象A的这个对象可能就要被迫修改代码。但如果将A包装一层,在访问对象A时就变成访问对象A的包装层,而包装层的变动肯定比对象A变动的几率小,所以此时即使对象A修改对象属性或行为,只要包装层不变动,其他的代码也就不需要变动。
对于对象的参数是不固定的,推荐使用Builder创建模式。
关于工厂模式,让我想到了电视剧《楚汉骄雄》中刘邦问韩信的一段话,当时刘邦准备封被萧何追回来的韩信为大将军。刘邦问他,他如何能指挥十万大军?韩信从容不迫的说,很容易,我只需要十个听话的将军即可。多么霸气的一段话,只需十个将军即可。
这个过程感觉和工厂模式有点类似,即我只抓最顶层,底层的实现我是不关心的。只要抓住了顶层,底层在怎么变化也不会影响整体。
类图
以下图片copy自知乎,能直观的查看几个工厂方法之间的差异,仅做笔记查看用,见具体链接。感谢原作者分享。
-
简单工厂模式
-
工厂模式
- 抽象工厂模式
总结
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
简单工厂模式 | 1、屏蔽产品的具体生产细节,调用者只关心生产产品的接口。 2、一个工厂类生产所有的产品,实现简单。 | 1、增加产品时需修改工厂类,不符合设计原则种的开放-封闭原则。 2、在工厂类集中了所有的对象创建逻辑,违反了高内聚的原则。 | 1、产品类较少且产品类不易变动的场景。 2、调用者不关心产品对象的创建,只知道传入工厂的类型参数。 |
工厂模式 | 1、继承了简单工厂模式的优点。2、符合开放-封闭的设计原则。 | 1、增加产品时需要增加额外的工厂类,一定程度增加了系统的复杂性。 | 1、产品类型容易变更的场景。 2、调用者不关心对象的创建,只关心产品对象。 |
抽象工厂模式 | 1、继承工厂模式的优点。 2、增加或替换产品组方便。 | 1、增加一类具体的子产品时较繁琐,需要创建不同类型产品类、修改现有工厂方法以增加对新产品支持。 | 1. 系统不应依赖于产品类实例如何被创建、组合和表达的细节。 2、系统不应依赖于产品类示例如何被创建、组合和表达的细节。 |
- 简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。
- 工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
- 抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。