一、抽象工厂定义
抽象工厂模式侧重于“产品族”的概念,有了这个概念之后,它允许工厂类中有多个抽象方法分别生产不同的对象,这些对象组成一个“产品族”,举个例子:
有这样一组对象:运输工具
+ 引擎
+ 控制器
。 它可能会有几个变体:
汽车
+内燃机
+方向盘
飞机
+喷气式发动机
+操纵杆
可以发现他们之间有规律的形成了“系列产品”,可以将系列产品的生产打包到一个工厂生产。
工厂方法模式和抽象工厂模式的异同:
工厂方法模式 | 抽象工厂模式 |
---|---|
针对的是单个产品等级结构 | 针对的是面向多个产品等级结构 |
一个抽象产品类 | 多个抽象产品类 |
可以派生出多个具体产品类 | 每个抽象产品类可以派生出多个具体产品类 |
一个抽象工厂类,可以派生出多个具体工厂类 | 一个抽象工厂类,可以派生出多个具体工厂类 |
每个具体工厂类只能创建一个具体产品类的实例 | 每个具体工厂类可以创建多个具体产品类的实例 |
二、案例
先来回顾下工厂方法模式下的“文件解析”案例:
使用了三个工厂分别生产三种解析器,似乎没什么问题,但是现在有个新需求:
张三和李四分别实现了自己的一套文件解析器,包含Xml、Csv以及Json,这如何将他们纳入到这个体系中呢?
解决方式一:还是用工厂方法模式
每个解析类和工厂类都派生出两个单独的子类,比如XmlFileParser
派生出ZhangSanXmlFileParser
和LiSiXmlFileParser
,XmlFileParserFactory
派生出ZhangSanXmlFileParserFactory
和LiSiXmlFileParserFactory
这样似乎能解决问题,但是类太多了,如果我要新增一种文件解析类型,一下得新增四个类:张三的文件解析器、张三的文件解析工厂、李四的文件解析器、李四的文件解析工厂。
似乎过于繁琐了。
解决方式二:使用抽象工厂模式
分析:张三、李四两个对象进来实际上是新增加了一个“维度”,这个维度比较高,甚至能把所有的文件解析器和工厂都分成“张三的”或者“李四”的,类图如下
这样就完成了抽象工厂模式的改造。现在工厂类能创建多种对象了,而且我将工厂类只划分了两类:张三和李四工厂类,工厂类生产的也只是各自的FileParser。
三、总结
抽象工厂模式的缺点:增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
抽象工厂模式的优点:工厂方法模式只有1种create, 当你要新增一类产品的时候, 得重新设计工厂接口类。 而抽象工厂中, 直接工厂接口类上新增方法, 后面的子类同一实现即可。 新增接口方法, 不需要再新增接口类
因此这个工厂更像一个有很多能力的大工厂了, 不再是一个简单的create()了
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
四、工厂模式的应用
工厂模式几乎在所有的框架中都有应用,以下看看几个应用
1、日志框架slf4j
先看一段常用的代码:
private static final Logger logger = LoggerFactory.getLogger(Application.class);
然后打印日志的时候调用logger的info、warn、error等方法。
很明显,从名字上就能看出来这里使用了工厂模式创建了Logger对象,依次点进去查看源代码,可以看到抽象工厂的顶层接口
它只有一个方法getLogger,而且它的返回值Logger也是一个interface
由此可知,它是一个很标准的工厂方法模式无疑了。
2、jdk的Calendar类
一段经典的代码
Calendar calendar = Calendar.getInstance();
看看它创建实例的方法
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
这段代码根据不同的timezone和Locale创建不同的Calendar实例,可以确认使用的是简单工厂模式,但是Calendar类又有些特殊,特殊之处就在于Calendar类不是以Factory作为后缀来命名的,而且,返回的对象是自身,在实际的开发中,工厂类的设计往往比较灵活。
参考文档:
https://bbs.huaweicloud.com/blogs/338666
https://developer.aliyun.com/article/780994?spm=a2c6h.13262185.profile.20.1d934622twKSqB
注意:本文归作者所有,未经作者允许,不得转载