在Java开发的道路上,想要走得更深远,设计模式是一座绕不开的大山。理解了设计模式可以让我们写出更加优雅的代码,做好项目重构,也有助于更好的理解各种经典框架中的设计思想和原理。学习设计模式的过程也有助于锻炼我们将业务需求转换成技术实现的能力。
先从工厂模式开始。工厂模式是属于创建型模式,主要适用于为调用者创建对象,例如Spring中的BeanFactory就是典型的工厂模式。而工厂模式又可以分为简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式
简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例。但是它不属于GOF总结的23种设计模式。
假设有一个罐头厂,它只生产苹果、橘子等罐头:
先定义一个标准罐头接口,所有的罐头在生产过程中都有罐装食物这个动作:
<code>public
interface
ICanned
{void
put
(); }/<code>
创建一个苹果罐头实现罐头ICanned类:
<code>public
class
AppleCanned
implements
ICanned
{public
void
put
()
{ System.out.println("灌入苹果"
); } }/<code>
如果我们要调用这个类,会这样写:
<code>ICanned apple =new
AppleCanned(); apple.put();/<code>
父类ICanned的指向子类AppleCanned的引用,所以应用层代码需要依赖AppleCanned。如果这个罐头厂业务扩展,增加了橘子罐头甚至更多种类的罐头,那么我们客户端的依赖就会变得越来越臃肿。所以我们要想办法减弱这种依赖,把创建细节隐藏。虽然目前代码创建对象的过程并不复杂,但是不利于扩展,所以使用简单工厂进行优化 .首先增加OrangeCanned类:
<code>public
class
OrangeCanned
implements
ICanned
{public
void
put
()
{ System.out.println("灌入橘子"
); } }/<code>
创建工厂类:
<code>public
class
CannedFactory
{public
static
ICannedcreate
(String name
) {if
("apple"
.equals
(name)) {return
new
AppleCanned(); }else
if
("orange"
.equals
(name)) {return
new
OrangeCanned(); }else
{throw
new
RuntimeException("不支持此对象创建"
); } } }/<code>
客户端调用可以修改为:
<code>public
class
CannedTest
{public
static
void
main
(String[] args)
{ CannedFactory factory =new
CannedFactory(); factory.create("apple"
).put(); } }/<code>
由此可以看到,生产苹果罐头时客户端不再依赖AppleCanned类,同样的生产橘子罐头时也不需要依赖OrangeCanned,客户端调用就变得简单了,不需要关心对象创建的细节。
虽然客户端调用变得简单,但是如果还要继续增加罐头的种类,那么就必须修改工厂类CannedFactory中create方法的代码实现逻辑,这样不符合代码设计原则中的开闭原则。所以,还可以对简单工厂进行优化:
<code>public
class
CannedFactory
{public
static
ICannedcreate
(String className
) {try
{if
(!(null
== className ||""
.equals
(className))) {return
(ICanned) Class.forName(className).newInstance(); } }catch
(Exception e) { e.printStackTrace(); }return
null
; } }/<code>
根据传入的className通过反射创建对象实例,客户端调用代码如下:
<code>public
class
CannedTest
{public
static
void
main
(String[] args)
{ CannedFactory factory =new
CannedFactory(); ICanned apple = factory.create("com.ll.patterns.factory.simple.AppleCanned"
); apple.put(); } }/<code>
修改之后,不管产品如何丰富,还要增加的多少种罐头,都不需要修改CannedFactory中的代码。这里还存在一个问题,create方法参数是一个字符串,存在不可控性,且通过反射创建对象时还需要进行强制转型,所以还应该让代码变得更健壮:
<code>public
class
CannedFactory
{public
static
ICanned create(Class
extends
ICanned
>clazz
) {try
{if
(null
!= clazz) {return
clazz.newInstance(); } }catch
(Exception
e) { e.printStackTrace(); }return
null
; } }/<code>
客户端调用代码修改为:
<code>public
class
CannedTest
{public
static
void
main
(String[] args)
{ CannedFactory factory =new
CannedFactory(); ICanned apple = factory.create(AppleCanned.
class
); apple.put(); } }/<code>
简单工厂模式在JDK中有广泛的运用,例如Calendar类的Calendar.getInstance()方法,日志记录时用到的LoggerFactory中有多个重载的getLogger()方法。
简单工厂模式的缺点也很明显:工厂类职责过重,不易于扩展复杂的产品结构,不适用于创建逻辑过于复杂的产品。
工厂方法模式
工厂方法模式(Factory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的实现类来决定创建哪个类,让类的实例化推迟到子类进行。在工厂方法模式中,用户只需要关心产品对应的工厂即可,无需关心产品的创建细节。
工厂方法模式主要是解决简单工厂中产品扩展的问题。首先,根据单一职责原则,将简单工厂中的职责进行进一步拆分,苹果罐头由苹果罐头工厂创建,橘子罐头由橘子罐头工厂创建;新增产品时,同时新增对应的工厂,不需要修改已有的工厂,符合开闭原则。
先对工厂本身做一个抽象,创建ICannedFactory接口:
<code>public
interface
ICannedFactory
{ICanned
create
(); }/<code>
再创建苹果罐头工厂AppleCannedFactory和橘子罐头工厂OrangeCannedFactory:
<code>import
com.ll.patterns.factory.AppleCanned;import
com.ll.patterns.factory.ICanned;public
class
AppleCannedFactory
implements
ICannedFactory
{public
ICannedcreate
()
{return
new
AppleCanned(); } }/<code>
<code>import
com.ll.patterns.factory.ICanned;import
com.ll.patterns.factory.OrangeCanned;public
class
OrangeCannedFactory
implements
ICannedFactory
{public
ICannedcreate
()
{return
new
OrangeCanned(); } }/<code>
客户端调用代码:
<code>public
class
CannedTest
{public
static
void
main
(String[] args)
{ ICannedFactory factory =new
AppleCannedFactory(); ICanned canned = factory.create(); canned.put(); factory =new
OrangeCannedFactory(); canned = factory.create(); canned.put(); } }/<code>
工厂方法适用于:
- 创建对象需要大量重复代码;
- 客户端不依赖于产品类实例如何创建、实现等细节;
- 一个类通过其子类指定创建对象。
缺点:
- 随着产品的增加,类的个数过多,增加复杂度;
- 增加了系统的抽象性和理解难度。
抽象工厂模式
抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。这里需要理解产品族和产品等级两个概念:
- 产品族是指一个同工厂里生产的不同种类的产品
- 产品等级是指不同工厂里生产的同一类型的产品
以罐头为例,每一个罐头包含了罐头、包装标签甚至食用工具等等。首先,在产品等级中增加罐头标签ILabel:
<code>public
interface
ILabel
{void
与前面创建的ICanned一起构成两个产品等级,然后创建一个抽象工厂ICannedFactory:
<code>import
com.ll.patterns.factory.ICanned;import
com.ll.patterns.factory.ILabel;public
interface
ICannedFactory
{ICanned
createCanned
()
;ILabel
createLabel
()
; } /<code>
抽象工厂是用户的主入口,在Spring中应用得最为广泛的一种设计模式,得益于该模式易于扩展。
接下来创建苹果罐头产品族,苹果罐头和苹果罐头标签,苹果罐头(AppleCanned)在前面已经创建了,这里只创建标签AppleLabel:
<code>public
class
AppleLabel
implements
ILabel
{public
void
()
{ System.out.println("打印苹果罐头标签"
); } } /<code>
创建苹果产品族的具体工厂AppleCannedFactory:
<code>import
com.ll.patterns.factory.AppleCanned;import
com.ll.patterns.factory.AppleLabel;import
com.ll.patterns.factory.ICanned;import
com.ll.patterns.factory.ILabel;public
class
AppleCannedFactory
implements
ICannedFactory
{public
ICannedcreateCanned
()
{return
new
AppleCanned(); }public
ILabelcreateLabel
()
{return
new
AppleLabel(); } }/<code>
再以同样的方式创建一个橘子罐头产品族,橘子罐头和橘子罐头标签,橘子罐头(OrangeCanned)在前面已经创建了,这里只创建标签OrangeLabel:
<code>public
class
OrangeLabel
implements
ILabel
{public
void
()
{ System.out.println("打印橘子罐头标签"
); } } /<code>
再创建橘子产品族的具体工厂OrangeCannedFactory:
<code>public
class
OrangeCannedFactory
implements
ICannedFactory
{public
ICannedcreateCanned
()
{return
new
OrangeCanned(); }public
ILabelcreateLabel
()
{return
new
OrangeLabel(); } }/<code>
最后客户端调用:
<code>public
class
CannedTest
{public
static
void
main
(String[] args)
{ ICannedFactory factory =new
AppleCannedFactory(); factory.createCanned().put(); factory.createLabel().print(); factory =new
OrangeCannedFactory(); factory.createCanned().put(); factory.createLabel().print(); } }/<code>
抽象工厂虽然能够很清晰的描述产品族和产品等级的关系,但是如果需要继续扩展产品等级,那么代码从抽象工厂,到具体的工厂全部都需要调整,很显然是不符合开闭原则的,这也是抽象工厂的缺点之一。另外,抽象工厂同样的也增加了系统的抽象性和理解的难度。
在实际应用中,我们可以根据实际情况,只要不是频繁的升级产品等级结构,可以不遵循开闭原则,半年、一年或者更长时间升级一次是完全可以接受的。