- 作者:老汪软件技巧
- 发表时间:2024-08-18 15:04
- 浏览量:
在 GoF 的《设计模式》一书中,工厂模式被分为了三种:简单工厂、工厂方法和抽象工厂。(不过,在书中作者将简单工厂模式看作是工厂方法模式的一种特例。)在实际工作中,用得比较多的就是工厂方法模式和抽象工厂模式这两类。本篇文章,我们就先看一下抽象工场模式。
学习抽象工厂模式真正的重点和难点在于:如何找到正确的抽象。虽然抽象工厂模式很容易实现,但更重要的是我们要能意识到“正确的抽象往往都很简单也很底层”,比如,数据库的增删改查操作,日志的 debug、info、warn、error 级别,JVM 内存模型,等等。其实,抽象工厂模式想要告诉我们的不只是在写代码时随便建个抽象类就够了,而是当我们自己在对一类功能进行抽象分析时有没有找到足够简单而又通用的正确抽象。
一、模式原理分析
抽象工厂模式的原始定义是:提供了一个用于创建相关或相关对象族的接口,而无须指定其具体类。
实际上,这句话是给使用者说的。因为从使用者的角度来看,他有时可能只关心某一个抽象的大类,就好比你去租车时,你对店员说,你想要租一辆小型轿车,但具体品牌和型号你并不在意。而站在创建者的角度看,他需要关注的是如何找到这个正确的抽象大类,就好比在上面的租车场景中,你需要从普通的汽车消费者变成汽车厂的厂长一样,你必须关注最后具体的型号该怎么落地。
所以说,当我们在创建抽象工厂模式时,最终还是会涉及指定具体的实现类。换句话说,定义只是说了抽象工厂模式应该要朝着分析共性规律的方向走,而具体操作时我们还得仔细分析具体实现类该怎么实现才行。
我们再来看看抽象工厂模式原始的 UML 图:
从这个 UML 图中,我们能看出抽象工厂模式中其实包含了四个关键角色。
为了便于更好理解,我们这里打个比方:比如说,抽象工厂生产的抽象产品是椅子、桌子、沙发一类的家具,那具体工厂可能就在生产具体的产品:椅子设计成现代简约风格或欧洲宫廷风格,使用的材质有木质或铝制,等等。本质上椅子的特性没有发生重大改变,但在外观上,不同的具体工厂生产的椅子尺寸、材质、外观各不相同。
其中最为关键的角色并不是抽象工厂本身,而是抽象产品。抽象产品的好坏才是直接决定了抽象工厂和具体工厂能否发挥最大作用的关键所在。这也是我们在前面原则模块和思维模块里多次提到的“找到正确的抽象很重要”的原因。
明白了这个道理后,再来看下面 UML 图的代码实现,会发现思路特别清晰。
public class Client {
private Chair myChair;
private Sofa mySofa;
private Table myTable;
//通过抽象工厂来生产家具
public Client(AbsractFactory af){
myChair = af.createChair();
mySofa = af.createSofa();
myTable = af.createTable();
}
}
//抽象的家具工厂
public abstract class AbsractFactory {
abstract Chair createChair();
abstract Sofa createSofa();
abstract Table createTable();
}
//中国的家具工厂
public class ChinaFactory extends AbsractFactory {
@Override
Chair createChair() {
return new ChinaChair();
}
@Override
Sofa createSofa() {
return new ChinaSofa();
}
@Override
Table createTable() {
return new ChinaTable();
}
}
//美国的家具工厂
public class USAFactory extends AbsractFactory{
@Override
Chair createChair() {
return new USAChair();
}
@Override
Sofa createSofa() {
return new USASofa();
}
@Override
Table createTable() {
return new USATable();
}
}
代码实现了两个不同国家的家具制造工厂。其中,AbsractFactory 是抽象工厂,创建的椅子、沙发和桌子是抽象产品;中国工厂和美国工厂是具体工厂,通过中国工厂或美国工厂制作的家具是具体产品。当我们只想买椅子、沙发和桌子时,只需要告诉抽象工厂就行,可能得到的是中国生产的,也可能是美国生产的。
从上面代码实现中我们可以看出,抽象工厂模式向使用(客户)方隐藏了下列变化:
这些变化也给了我们一些启示,对于软件使用者来说,他们其实更关心某一组产品的某些共性功能,至于这些功能具体的实现他们并不在意。反过来,对于软件创建者来说,他们要找到正确的共性功能,并尽可能隐藏具体的实现细节,始终围绕着提供符合共性功能的软件。比如,Spring 框架就是始终围绕着如何正确地管理(创建、使用、销毁)Java 对象生命周期这个共性功能。
所以说,在理解抽象工厂模式原理时,一定要牢牢记住“如何找到某一个类产品的正确共性功能”这个重点。
二、使用场景分析
实际上,抽象工厂模式在现实中有很多应用。
比如,当我们需要在一个应用程序中支持多个操作系统时,就会用到像抽象工厂模式这样的机制,需要为目前应用程序所使用的操作系统(Windows、Mac、Linux)选择正确的硬件驱动程序集合(包括磁盘驱动程序、显示驱动程序、IO 外设驱动程序等)。
再比如,在电商系统中,国内电商和海外电商都需要使用类似商品、订单、物流等系统,但是不同地区的政策条件不同、购买习惯不同,即便是同样的线上购物流程,也会存在不同的具体代码实现。这种情况下使用抽象工厂模式就是一个很好的方式,不仅能提高代码的可移植性,还能找到不同地区的差异性。
简单来说,在软件开发中,抽象工厂模式的使用场景主要就是解决跨平台兼容性的问题。
这里我们还是通过一个例子来帮助你理解抽象工厂模式的使用场景。在 Spring 框架中的 BeanFactory 就是最早实现抽象工厂模式的代码,如下所示:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
T getBean(String var1, Class var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
T getBean(Class var1) throws BeansException;
T getBean(Class var1, Object... var2) throws BeansException;
ObjectProvider getBeanProvider(Class var1) ;
ObjectProvider getBeanProvider(ResolvableType var1);
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
BeanFactory 在 Spring 中是实现 IoC 容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。实现这个接口的抽象工厂类有很多,比如,AbstractBeanFactory 等。
而围绕 AbstractBeanFactory 的具体实现类,有 DefaultListableBeanFactory、XmlBeanFactory 等。这里,BeanFactory 就是抽象产品,AbstractBeanFactory 是抽象工厂,XmlBeanFactory 是具体工厂,通过 XML 注入的 Bean 实例就是最终通过 XmlBeanFactory 具体实现的产品。
总结来说,在实际的代码实现中,抽象工厂模式体现为定义一个抽象工厂类,多个不同的具体工厂继承这个抽象工厂类后,再各自实现相同的抽象功能,进而实现代码上的多态性。
三、为什么要使用工厂模式
分析完抽象工厂模式的原理和使用场景后,我们再来说说使用抽象工厂模式的原因,主要有以下三点。
第一点,对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性。 比如,不同的数据库产品,JDBC 就是对于数据库增删改查建立的抽象工厂模式,无论使用什么类型的数据库,只要具体的数据库组件能够支持 JDBC,就能对数据库进行读写操作,这极大地提高了我们对不同数据库组件的复用性。
第二点,当需要提升代码的扩展性并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上。比如,你需要创建统一的日志监控,但不同应用使用的日志收集代理可能各不相同,这时如果有一个统一的日志收集工厂定义抽象的日志收集功能,那么不同的代理只需要按照各自的实现方式提供统一的日志收集功能即可,这样即便以后新增了一些代理,也不会影响旧的功能,提升扩展性的同时也能提升维护性。
第三点,解决跨平台带来的兼容性问题。 抽象工厂模式提供了一种解决跨平台问题的思路,也就是我们的后台服务应该尽可能地使用更高层级的统一的抽象功能,然后通过不同客户端的适配程序来实现统一的功能交付。比如,同一个地区里,安卓或 iOS 的客户端 App 通过 API 网关访问商品数据时,应该是先获取统一的抽象数据对象,然后经过安卓或 iOS 的客户端适配器程序的适配转换和传输,而不是针对具体型号的手机(华为、小米、苹果等)来单独进行适配,安卓和 iOS 这里就被看作是不同手机型号的抽象工厂模式。
四、抽象工厂模式有什么优缺点?
那使用抽象工厂模式我们能收获什么呢?也就是抽象工厂模式的优点有哪些呢?
同样,除了以上优点外,抽象工厂模式也有一些缺点。