【Java设计模式系列】工厂方法模式(中)

案例二

注册工厂

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() 以生成它们中的一个。


工厂方法模式变种较多,看个比较实用的通用源码。


抽象产品类

【Java设计模式系列】工厂方法模式(中)

具体产品类,可以有多个,都继承于抽象产品类

【Java设计模式系列】工厂方法模式(中)

抽象工厂类,定义产品对象的产生

【Java设计模式系列】工厂方法模式(中)

具体工厂类,具体如何产生一个产品的对象,由具体工厂类实现

【Java设计模式系列】工厂方法模式(中)

场景类

【Java设计模式系列】工厂方法模式(中)

该通用代码是一个比较实用、易扩展的框架,读者可以根据实际项目需要进行扩展

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修改一下

【Java设计模式系列】工厂方法模式(中)

我们在类图中去掉了AbstractHumanFactory抽象类,同时把createHuman方法设置为静态类型,简化了类的创建过程,变更的源码仅仅是HumanFactory和NvWa类

  • 简单工厂模式中的工厂类
  • 【Java设计模式系列】工厂方法模式(中)
  • HumanFactory类仅有两个地方发生变化
  • 去掉继承抽象类
  • createHuman前增加static关键字

工厂类发生变化,也同时引起了调用者NvWa的变化

【Java设计模式系列】工厂方法模式(中)

运行结果没有发生变化,但是我们的类图变简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以称为简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式

在实际项目中,采用该方法的案例还是比较多的


其缺点

工厂类的扩展比较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。


上一篇:Tomcat https自制证书和浏览器配置


下一篇:tomcat证书配置