JAVA设计模式之工厂模式(三种工厂模式)
Posted 随逸星
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA设计模式之工厂模式(三种工厂模式)相关的知识,希望对你有一定的参考价值。
1.工厂模式可以分为三类:
-
简单工厂模式(Simple Factory)
-
工厂方法模式(Factory Method)
-
抽象工厂模式(Abstract Factory)
简单工厂其实不是一个标准的的设计模式。GOF 23 种设计模式中只有「工厂方法模式」与「抽象工厂模式」。简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式。
这三种工厂模式在设计模式的分类中都属于创建型模式,三种模式从上到下逐步抽象。
2.创建型模式
创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。
创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。
工厂模式是创建型模式中比较重要的。工厂模式的主要功能就是帮助我们实例化对象。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替 new 操作的。
3.工厂模式优点:
-
可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
-
对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
-
降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
4.适用场景
不管是简单工厂模式,工厂方法模式还是抽象工厂模式,他们具有类似的特性,所以他们的适用场景也是类似的。
首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
一、简单工厂模式
1、简单工厂模式的角色
简单工厂模式的角色如下:
角色 | 解释 |
---|---|
简单工厂SimpleFactory | 负责创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。 |
抽象产品IProduct | 简单工厂创建的多有对象的父类,负责描述所有实例共有的公共接口。 |
具体产品ConcreteProduct | 简单共产模式的创建目标 |
2、UML 类图
3、简单工厂模式的通用写法
抽象产品类IProduct:
public interface IProuduct
void doSomeThing();
具体产品类ProductA:
@Slf4j
public class ProductA implements IProuduct
@Override
public void doSomeThing()
log.info("我是ProductA");
具体产品类ProductB:
@Slf4j
public class ProductB implements IProuduct
@Override
public void doSomeThing()
log.info("我是ProductB");
简单工厂类SimpleFactory(通过传入的productName来决定生成哪个具体产品)
public class SimpleFactory
static IProuduct makeProduct(String productName)
if ("ProductA".equals(productName))
return new ProductA();
else if ("ProductB".equals(productName))
return new ProductB();
else
return null;
客户端Client类:
public class Client
public static void main(String[] args)
// 生成产品B
IProuduct product = SimpleFactory.makeProduct("ProductB");
product.doSomeThing();
在简单工厂模式中,抽象产品既可以是各个具体产品类实现的共同的接口,也可以是各个具体产品类继承的抽象父类。
4、 简单工厂模式总结
优点:
简单工厂模式,封装了创建对象的逻辑,完成了创建对象逻辑与业务代码逻辑的解耦。试想客户端是多个service层的文件,对比不使用简单工厂模式,当我们要改变产生对象的逻辑时,需要在多个service文件中找到这部分代码进行修改。在使用简单工厂模式后,只需要修改简单工厂中生成对象的逻辑即可,不需要修改业务代码。完成了解耦。
缺点:
每当具体产品类的抽象产品类增多时,会需要在简单工厂类中新增关于新增产品类对象生成的方法。当抽象产品类很多时,抽象工厂会很臃肿。并且在这种情形下,SimpleFactory类也不符合开闭原则。
二、工厂方法模式
工厂方法模式:定义创建对象的接口,但具体实现放到实现这个接口的管理具体一类产品的对象生成的工厂类中去实现。
1、工厂方法模式的角色
角色 | 解释 |
---|---|
抽象工厂 | 定义了创建抽象产品的方法,任何在模式中创建对象的工厂类都必须实现这个接口 |
具体工厂 | 实现抽象工厂接口的具体工厂类,负责生产具体的产品 |
抽象产品 | 工厂方法模式所创建的对象的超类型。也是产品对象的共同父类或者共同拥有的接口 |
具体产品 | 实现了抽象产品角色所定义的接口。某具体产品由具体工厂创建,他们往往一一对应 |
2、UML 类图
3、工厂方法模式的通用写法
抽象工厂:
/**
* 抽象工厂
*/
public interface IFactory
IProduct makeProduct();
抽象产品:
/**
* 抽象产品
*/
public interface IProduct
void doSomeThing();
具体产品ProductA:
/**
* 具体产品A
*/
@Slf4j
public class ProductA implements IProduct
@Override
public void doSomeThing()
log.info("我是productA");
具体产品ProductB:
/**
* 具体产品B
*/
@Slf4j
public class ProductB implements IProduct
@Override
public void doSomeThing()
log.info("我是ProductB");
生产ProductA的具体工厂FactoryA:
/**
* 生产ProductA的具体工厂
*/
public class FactoryA implements IFactory
@Override
public IProduct makeProduct()
return new ProductA();
生产ProductB的具体工厂FactoryB:
/**
* 生产ProductB的具体工厂
*/
public class FactoryB implements IFactory
@Override
public IProduct makeProduct()
return new ProductB();
客户端:
public class Client
public static void main(String[] arges)
// 生产ProductA
FactoryA factoryA = new FactoryA();
factoryA.makeProduct().doSomeThing();
4、工厂方法模式适用场景
工厂方法模式和简单工厂模式虽然都是通过工厂来创建对象,他们之间最大的不同是——工厂方法模式在设计上完全完全符合“开闭原则”。
在以下情况下可以使用工厂方法模式:
-
一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
-
一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
-
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
使用场景:
-
日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
-
数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
-
设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
-
比如 Hibernate 换数据库只需换方言和驱动就可以
5、工厂方法模式总结
从简单工厂模式的讲述知道:简单工厂的一个缺点在于,每当需要新增产品时,都需要修改负责生产产品的SimpleFactory类,违背了“开闭原则”,并且会使SimpleFactory类十分的臃肿。而使用工厂方法模式后,当新增ProductC时,只需要对应创建具体产品类ProductC和负责生产ProductC的具体工厂FactoryC即可。符合“开闭原则”,便于扩展。
它的缺点在于:
(1)类的个数容易过多,增加复杂度
(2)实现抽象工厂接口的具体工厂只能生产出一种产品(可以用抽象工厂模式解决)
三、抽象工厂模式
抽象工厂模式在工厂方法模式的基础上进行进一步抽象。设想下面这种场景:
现有两种具体产品(具体产品):篮球,足球(在此基础上的抽象产品可看成球)。同时,对于足球和篮球来说,他们都有两种品牌安踏、李宁。
如果采用工厂方法模式解决上述场景中创建产品的问题,需要在抽象工厂中定义创建产品的方法,并新建四个用于创建具体产品的具体工厂类:用于创建“李宁篮球”的具体工厂,用于创建“李宁足球”的具体工厂,用于创建“安踏篮球”的具体工厂,用于创建“安踏足球”的具体工厂。
从上面的解决方式可以看出:使用工厂方法模式,创建了很多具体工厂类,而没有利用产品的“商品族”的概念。
由此引出,抽象工厂模式是用于解决“一类产品”的创建问题(在上述场景中,可以把“李宁篮球”,“李宁足球”,“安踏篮球”,“安踏足球”归纳为:“篮球”,“足球”这两类商品)
1、抽象工厂模式的角色
角色 | 解释 |
---|---|
抽象工厂 | 声明创建抽象产品对象的一个接口(有几个产品组,则声明几个方法。比如对于上述的场景,需要声明一个用于生产篮球类产品的方法,还需要声明一个用于生产足球类产品的方法) |
具体工厂 | 实现创建具体产品对象的操作 |
抽象产品 | 为一类产品对象的抽象 |
具体产品 | 定义一个将被相应的具体工厂创建的产品对象(比如上述场景中的:李宁篮球) |
2、UML 类图
3、抽象工厂模式的通用写法
抽象工厂:
/**
* 抽象工厂
*/
public interface AbstractFactory
Basketball makeBasketball();
Football makeFootball();
抽象产品族篮球:
/**
* 抽象产品族;篮球
*/
public interface Basketball
void sayBasketball();
抽象产品族足球:
/**
* 抽象产品族:足球
*/
public interface Football
void sayFootball();
四个具体产品:李宁篮球、李宁足球、安踏篮球、安踏足球
/**
* 具体产品:李宁篮球
*/
@Slf4j
public class LiningBasketball implements Basketball
@Override
public void sayBasketball()
log.info("我是李宁篮球");
/**
* 具体产品:李宁足球
*/
@Slf4j
public class LiningFootball implements Football
@Override
public void sayFootball()
log.info("我是李宁足球");
/**
* 具体产品:安踏篮球
*/
@Slf4j
public class AntaBasketball implements Basketball
@Override
public void sayBasketball()
log.info("我是安踏篮球");
/**
* 具体产品:安踏足球
*/
@Slf4j
public class AntaFootball implements Football
@Override
public void sayFootball()
log.info("我是安踏足球");
具体工厂:
/**
* 具体工厂,负责生产李宁篮球,李宁足球
*/
public class LiningFactoy implements AbstractFactory
@Override
public Basketball makeBasketball()
return new LiningBasketball();
@Override
public Football makeFootball()
return new LiningFootball();
/**
* 具体工厂,负责生产安踏篮球,安踏足球
*/
public class AntaFactory implements AbstractFactory
@Override
public Basketball makeBasketball()
return new AntaBasketball();
@Override
public Football makeFootball()
return new AntaFootball();
客户端:
public class Client
public static void main(String[] args)
// 生产李宁篮球和安踏足球
LiningFactoy liningFactoy = new LiningFactoy();
AntaFactory antaFactory = new AntaFactory();
liningFactoy.makeBasketball().sayBasketball();
antaFactory.makeFootball().sayFootball();
4、抽象工厂模式适用场景
抽象工厂模式和工厂方法模式一样,都符合开闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。
在以下情况下可以使用抽象工厂模式:
-
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
-
系统中有多于一个的产品族,而每次只使用其中某一产品族。
-
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
-
系统结构稳定,不会频繁的增加对象。
“开闭原则”的倾斜性
在抽象工厂模式中,增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为**“开闭原则”的倾斜性**。“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:
-
增加产品族:对于增加新的产品族,工厂方法模式很好的支持了“开闭原则”,对于新增加的产品族,只需要对应增加一个新的具体工厂即可,对已有代码无须做任何修改。
-
增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。
正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。
5、抽象工厂模式总结
抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和 API 类库的设计中,例如在 Java 语言的 AWT(抽象窗口工具包)中就使用了抽象工厂模式,它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。抽象工厂模式也是在软件开发中最常用的设计模式之一。
优点:
-
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
-
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
-
增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
工厂模式的退化
当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
设计模式 - 工厂模式
概述
我们都知道
Java
中共有 23 种设计模式,其中工厂模式分为三种,即:简单工厂模式(不在 23 种设计模式之列)、工厂方法模式和抽象工厂模式;我们平时说的工厂模式,其实大都指工厂方法模式,这种模式是我们平时编码中用的频率最高的一种,在Spring
源码中就有很多工厂模式的应用,比如BeanFactory
。
下面依次按照简单工厂模式、工厂方法模式、抽象工厂模式的顺序,依次由浅入深说说这三种模式;文章分别从定义、场景、优缺点也示例进行讲解。
简单工厂模式
定义
简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例,简单来说就是,
定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。
场景
简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。总结一下就是:
- 需要创建的对象较少;
- 客户端不关心对象的创建过程;
优缺点
优点
实现了对责任的分割,提供了专门的工厂类用于创建对象
缺点
工厂类的职责相对过重,不易于扩展过于复杂的产品结构,不符合开闭原则(可解决)
示例
接下来我们构造一个场景来看看简单工厂模式的应用:现在手机更新换代的比较快,手机厂商每年基本都会在不同时间或者在同一时间发布生产不同型号和配置的手机。
假设某手机公司最近发布了型号为 A、B 的手机,其中生产任务交给代工厂去生产;我们都知道不管什么类型的手机都属于手机,所以我们先创建一个手机类Phone
,并在其中声明一个公共的手机型号方法type
:
/**
* @author eamon.zhang
* @date 2019-09-27 上午10:55
*/
public interface Phone {
void type();
}
然后定义具体的手机类型:
型号 A:
/**
* @author eamon.zhang
* @date 2019-09-27 上午11:02
*/
public class PhoneA implements Phone {
@Override
public void type() {
System.out.println("型号为A的手机!");
}
}
型号 B:
/**
* @author eamon.zhang
* @date 2019-09-27 上午11:03
*/
public class PhoneB implements Phone {
@Override
public void type() {
System.out.println("型号为B的手机!");
}
}
创建手机代工厂 PhoneFactory
类:
/**
* @author eamon.zhang
* @date 2019-09-27 上午10:54
*/
public class PhoneFactory {
public Phone product(String type) {
switch (type) {
case "A":
return new PhoneA();
case "B":
return new PhoneB();
default:
return null;
}
}
}
测试:
/**
* @author eamon.zhang
* @date 2019-09-27 上午11:09
*/
public class PhoneFactoryTest {
@Test
public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product("A").type();
phoneFactory.product("B").type();
}
}
输出:
型号为A的手机!
型号为B的手机!
当然,为了方便调用,
PhoneFactory
中的product()
也可以写成静态的。
类图:
拓展
解决不符合开闭原则问题
上面的示例中,客户端调用是简单了,但如果我们业务继续扩展,增加一个型号 C,那么上面的工厂方法中的product()
方法就得再次修改逻辑。不符合开闭原则;因此我们客户考虑对其进行进一步优化,利用反射技术修改product()
方法:
public Phone product(String className) {
try {
if (!(null == className || "".equals(className))) {
return (Phone) Class.forName(className).newInstance();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
修改客户端调用代码:
public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneA").type();
phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneB").type();
}
经过优化之后,今后再增加型号,就不用去修改工厂方法了;但是又有一个问题,方法参数是很长的字符串,可控性有待提升,而且还需要强制转型,不方便阅读和维护,所以进一步改造:
public Phone product(Class<? extends Phone> clazz) {
try {
if (null != clazz) {
return clazz.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
优化客户端调用代码:
@Test
public void product() {
PhoneFactory phoneFactory = new PhoneFactory();
phoneFactory.product(PhoneA.class).type();
phoneFactory.product(PhoneB.class).type();
}
再来看一下类图:
其他
简单工厂模式在 JDK 源码中也无处不足,比如常用的 Calendar
类中Calendar.getInstance()
方法,跟进源码到createCalendar(TimeZone zone,Locale aLocale)
就可以看出。
还有就是 常用的logback
,我们可以看到 LoggerFactory
中有多个重载的方法 getLogger()
:
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public final Logger getLogger(final Class<?> clazz) {
return getLogger(clazz.getName());
}
工厂方法模式
定义
工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这个 接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。
在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符 合开闭原则。
工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个手机的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,并不便于维护。根据单一职责原则我们将职能继续拆分,专人干专事。
场景
工厂方法适用于以下场景:
- 创建对象需要大量重复的代码。
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
- 一个类通过其子类来指定创建哪个对象。
优缺点
优点
- 具有良好的封装性,代码结构清晰,井底了模块间的耦合。
- 拓展性非常优秀。(在增加产品类的情况下,只要修改具体的工厂类或扩展一个工厂类)
- 屏蔽了产品类。(产品类的实现如何变化,调用者不需要关心)
缺点:
1、类的个数容易过多,增加复杂度。
2、增加了系统的抽象性和理解难度。
示例
A 型号手机由PhoneA
工厂创建,B 型号手机由PhoneB
工厂创建,对工厂本身也做一个抽象。来看代码,先创建 PhoneFactory
接口:
/**
* @author eamon.zhang
* @date 2019-09-27 下午1:45
*/
public interface PhoneFactory {
Phone product();
}
分别创建子工厂 PhoneAFactory
:
/**
* @author eamon.zhang
* @date 2019-09-27 下午1:50
*/
public class PhoneAFactory implements PhoneFactory {
@Override
public Phone product() {
return new PhoneA();
}
}
PhoneBFactory
类:
/**
* @author eamon.zhang
* @date 2019-09-27 下午1:50
*/
public class PhoneBFactory implements PhoneFactory {
@Override
public Phone product() {
return new PhoneB();
}
}
看测试代码:
/**
* @author eamon.zhang
* @date 2019-09-27 下午1:54
*/
public class PhoneFactoryTest {
@Test
public void product() {
PhoneFactory factory = new PhoneAFactory();
factory.product().type();
factory = new PhoneBFactory();
factory.product().type();
}
}
测试结果:
型号为A的手机!
型号为B的手机!
再看一下类图:
拓展
再来看看 logback 中工厂方法模式的应用,看看类图就 OK 了:
抽象工厂模式
定义
抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。
客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
理解
为了便于大家理解抽象工厂,我们先了解两个概念产品等级结构和产品族,看下面的图:
从上图中看出有正方形,圆形和三角形三种图形,相同颜色深浅的就代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以从生活中来举例,比如,美的电器生产多种家用电器。那么上图中,颜色最深的正方形就代表美的洗衣机、颜色最深的圆形代表美的空调、颜色最深的三角形代表美的热水器,颜色最深的一排都属于美的品牌,都是美的电器这个产品族。再看最右侧的三角形,颜色最深的我们指定了代表美的热水器,那么第二排颜色稍微浅一点的三角形,代表海信的热水器。同理,同一产品结构下还有格力热水器,格力空调,格力洗衣机。
再看下面这张图,最左侧的箭头代表具体的工厂,有美的工厂、海信工厂、格力工厂。每个品牌的工厂都生产洗衣机、热水器、空调。
通过上面两张图的对比理解,相信大家对抽象工厂有了非常形象的理解。
场景
一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。简单来说:
- 和工厂方法一样客户端不需要知道它所创建的对象的类。
- 需要一组对象共同完成某种功能时。并且可能存在多组对象完成不同功能的情况。
- 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)
优缺点
优点
- 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是接口,不关心对象是如何创建的,只要知道工厂类是谁,就能创建出一个需要的对象,省时省力。
- 产品族内的约束为非公开状态。
缺点
- 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
- 增加了系统的抽象性和理解难度
示例
比如现在有一个应用,假如是某视频软件,需要在三个不同的平台(Windows、IOS、Android)上运行,该应用针对每套系统都设计了一套上传控制器(UploadController
)、播放控制(DisplayController
),下面通过抽象工厂模式来设计该软件。
视频软件里边的各个平台的UploadController
和DisplayController
应该是我们最终生产的具体产品。所以新建两个抽象产品接口。
UploadController
接口:
/**
* @author eamon.zhang
* @date 2019-09-27 下午2:59
*/
public interface UploadController {
void upload();
}
DisplayController
接口:
/**
* @author eamon.zhang
* @date 2019-09-27 下午2:59
*/
public interface DisplayController {
void display();
}
定义抽象工厂VideoPlayerFactory
类,它能够创建UploadController
和DisplayController
:
/**
* 抽象工厂是主入口,在Spring中应用的最广泛的一种设计模式,易于扩展
*
* @author eamon.zhang
* @date 2019-09-27 下午3:04
*/
public interface VideoPlayerFactory {
DisplayController createDisplayController();
UploadController createUploadController();
}
然后在各个平台创建具体的 UploadController
和DisplayController
:
创建适用于Windows的UploadController
和DisplayController
:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class WindowsUploadController implements UploadController {
@Override
public void upload() {
System.out.println("Windows 上传控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class WindowsDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("Windows 上的播放器!");
}
}
创建适用于IOS的UploadController
和DisplayController
:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:10
*/
public class IosUploaderController implements UploadController {
@Override
public void upload() {
System.out.println("IOS 上传控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class IosDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("IOS 上的播放器!");
}
}
创建适用于Android的UploadController
和DisplayController
:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:10
*/
public class AndroidUploaderController implements UploadController {
@Override
public void upload() {
System.out.println("Android 上传控制器!");
}
}
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:09
*/
public class AndroidDisplayController implements DisplayController {
@Override
public void display() {
System.out.println("Android 上的播放器!");
}
}
在各平台具体的工厂类中完成上传控制器和播放控制器的创建过程:
创建WindowsFactory
类:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:15
*/
public class WindowsFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new WindowsDisplayController();
}
@Override
public UploadController createUploadController() {
return new WindowsUploadController();
}
}
创建IosFactory
类:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:17
*/
public class IosFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new IosDisplayController();
}
@Override
public UploadController createUploadController() {
return new IosUploaderController();
}
}
创建AndroidFactory
类:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:18
*/
public class AndroidFactory implements VideoPlayerFactory {
@Override
public DisplayController createDisplayController() {
return new AndroidDisplayController();
}
@Override
public UploadController createUploadController() {
return new AndroidUploaderController();
}
}
来看客户端调用:
/**
* @author eamon.zhang
* @date 2019-09-27 下午3:20
*/
public class VideoPlayerFactoryTest {
@Test
public void VideoPlayer() {
VideoPlayerFactory factory = new WindowsFactory();
// IOS
// factory = new IosFactory();
// // Android
// factory = new AndroidFactory();
UploadController uploadController = factory.createUploadController();
DisplayController displayController = factory.createDisplayController();
uploadController.upload();
displayController.display();
}
}
以调用 Windows 为例,结果:
Windows 上传控制器!
Windows 上的播放器!
上面就是针对不同平台只通过创建对应的工厂对象就完成了上传控制器和播放控制器的创建。抽象工厂非常完美清晰地描述这样一层复杂的关系。但是,不知道大家有没有发现,如果我们再继续扩展功能,将下载器也加入到产品中,那么我们的代码从抽象工厂,到具体工厂要全部调整,很显然不符合开闭原则。因此就有了上面优缺点中所说的缺点。
总结
在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级是非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭原则。代码每半年升级一次或者每年升级一次又有何不可呢?
源码:github.com
以上是关于JAVA设计模式之工厂模式(三种工厂模式)的主要内容,如果未能解决你的问题,请参考以下文章