案例二
注册工厂
Pet 层次生成对象的问题
每当添加一种新Pet 类型,必须记住将其添加到 LiteralPetCreator.java 的条目中。在一个定期添加更多类的系统中,这可能会成为问题。
你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。但静态初始值设定项仅在首次加载类时调用:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。
必须自己手工创建列表。所以最好就是把列表集中放在一个明显的地方:层次结构的基类
使用工厂方法设计模式将对象的创建推迟到类本身。
工厂方法以多态方式调用,创建适当类型的对象。
java.util.function.Supplier 用 T get() 描述了原型工厂方法。协变返回类型允许 get() 为 Supplier 的每个子类实现返回不同的类型。
在本例中,基类 Part 包含一个工厂对象的静态列表,列表成员类型为 Supplier<Part>。对于应该由 get() 方法生成的类型的工厂,通过将它们添加到 prototypes 列表向基类“注册”。奇怪的是,这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的原型:
// typeinfo/RegisteredFactories.java // 注册工厂到基础类 import java.util.*; import java.util.function.*; import java.util.stream.*; class Part implements Supplier<Part> { @Override public String toString() { return getClass().getSimpleName(); } static List<Supplier<? extends Part>> prototypes = Arrays.asList( new FuelFilter(), new AirFilter(), new CabinAirFilter(), new OilFilter(), new FanBelt(), new PowerSteeringBelt(), new GeneratorBelt() ); private static Random rand = new Random(47); public Part get() { int n = rand.nextInt(prototypes.size()); return prototypes.get(n).get(); } } class Filter extends Part {} class FuelFilter extends Filter { @Override public FuelFilter get() { return new FuelFilter(); } } class AirFilter extends Filter { @Override public AirFilter get() { return new AirFilter(); } } class CabinAirFilter extends Filter { @Override public CabinAirFilter get() { return new CabinAirFilter(); } } class OilFilter extends Filter { @Override public OilFilter get() { return new OilFilter(); } } class Belt extends Part {} class FanBelt extends Belt { @Override public FanBelt get() { return new FanBelt(); } } class GeneratorBelt extends Belt { @Override public GeneratorBelt get() { return new GeneratorBelt(); } } class PowerSteeringBelt extends Belt { @Override public PowerSteeringBelt get() { return new PowerSteeringBelt(); } } public class RegisteredFactories { public static void main(String[] args) { Stream.generate(new Part()) .limit(10) .forEach(System.out::println); } }
并非层次结构中的所有类都应实例化;这里的 Filter 和 Belt 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 Part 基类的行为)。
因为 Part implements Supplier<Part>,Part 通过其 get() 方法供应其他 Part。如果为基类 Part 调用 get()(或者如果 generate() 调用 get()),它将创建随机特定的 Part 子类型,每个子类型最终都从 Part 继承,并重写相应的 get() 以生成它们中的一个。
工厂方法模式变种较多,看个比较实用的通用源码。
抽象产品类
具体产品类,可以有多个,都继承于抽象产品类
抽象工厂类,定义产品对象的产生
具体工厂类,具体如何产生一个产品的对象,由具体工厂类实现
场景类
该通用代码是一个比较实用、易扩展的框架,读者可以根据实际项目需要进行扩展
3 应用
3.1 优点
- 用户只需要关心所需产品对应的工厂,无须关心创建细节
加入新产品符合开闭原则,提高可扩展性
良好的封装性,代码结构清晰
一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合
工厂方法模式的扩展性非常优秀
在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成“拥抱变化”
例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。
屏蔽产品类
这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化
因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的
在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
典型的解耦框架
高层模块值需要知道产品的抽象类,其他的实现类都不用关心
符合迪米特法则,我不需要的就不要去交流
也符合依赖倒置原则,只依赖产品类的抽象
当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
3.2 缺点
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
3.3 适用场景
- 创建对象需要大量重复的代码
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 一个类通过其子类来指定创建哪个对象
- 工厂方法模式是new一个对象的替代品
在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
需要灵活的、可扩展的框架时
万物皆对象,那万物也就皆产品类。
例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP。
可以把这三种连接方法作为产品类,定义一个接口如IConnectMail
然后定义对邮件的操作方法
用不同的方法实现三个具体的产品类(也就是连接方式)
再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式
如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了
异构项目
例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
使用在测试驱动开发的框架下
例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或EasyMock
4 扩展
工厂方法模式有很多扩展,而且与其他模式结合使用威力更大,下面将介绍4种扩展。
4.1 缩小为简单工厂模式
我们这样考虑一个问题:一个模块仅需要一个工厂类,没有必要把它产生出来,使用静态的方法就可以了,根据这一要求,我们把上例中的AbstarctHumanFactory
修改一下
我们在类图中去掉了AbstractHumanFactory
抽象类,同时把createHuman
方法设置为静态类型,简化了类的创建过程,变更的源码仅仅是HumanFactory和NvWa类
- 简单工厂模式中的工厂类
- HumanFactory类仅有两个地方发生变化
- 去掉继承抽象类
- 在
createHuman
前增加static关键字
工厂类发生变化,也同时引起了调用者NvWa的变化
运行结果没有发生变化,但是我们的类图变简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以称为简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式
在实际项目中,采用该方法的案例还是比较多的
其缺点
工厂类的扩展比较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。