设计模式之 ==> 工厂设计模式

一、简单工厂模式

简单工厂模式就是由一个工厂对象来决定创建出哪一种类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,先看下面这个图:

  可以看出,上面总共有三种类,一个简单工厂类,一个产品类(或接口)和三个具体的产品衍生类(子类)。其中,工厂类负责整个创建产品衍生的逻辑判断,所以为了使工厂类知道我们需要创建哪一种产品,需要在创建产品时传递给工厂类一个参数,表明我们需要创建的是哪一种产品。下面我们来用代码进行说明:

首先来一个产品接口:Product

public interface Product {

  void create();
}
Product

再来两个产品类:ProductA 和 ProductB

public class ProductA implements Product {

  @Override
  public void create() {
    System.out.println("create product A success!");
  }
}
ProductA
public class ProductB implements Product{

  @Override
  public void create() {
    System.out.println("create product B success!");
  }
}
ProductB

最后是工厂类:ProductFactory

public class ProductFactory {

  private ProductFactory() {
    //
  }

  private static class ClassHolder {
    private static final ProductFactory INSTANCE = new ProductFactory();
  }

  public static ProductFactory getInstance() {
    return ClassHolder.INSTANCE;
  }

  public Product createProduct(String productName) {

    if (productName == null) {
      return null;
    }

    switch (productName) {
      case "A":
        return new ProductA();
      case "B":
        return new ProductB();
      default:
        throw new IllegalStateException("没有这种产品:" + productName);
    }
  }
}
ProductFactory

来看下客户端调用

public class App {

  public static void main(String[] args) {
    Product productA = ProductFactory.getInstance().createProduct("A");
    productA.create();
    Product productB = ProductFactory.getInstance().createProduct("B");
    productB.create();
    Product productC = ProductFactory.getInstance().createProduct("C");
    productC.create();
 }

  总结起来就是一个工厂类,一个产品接口(其实也可以是一个抽象类,甚至一个普通的父类,但通常我们觉得接口是最稳定的,所以基本不需要考虑普通父类的情况),和一群实现了产品接口的具体产品,而这个工厂类,根据客户端传入的参数去创造一个具体的产品实例,并向上转型为接口作为结果返回。

  简单工厂模式是设计模式中比较简单的一种,在项目规模比较小的时候,它能够帮我们解决一些问题,但是在大型项目中,它的局限性就比较大。试想一下,如果我们的产品类型比较多,在后续项目迭代中需要增加成千上万种产品,我们除了要建立成千上万种产品以外,还要修改 ProductFactory 工厂类中的逻辑,增加成千上万个分支判断,这样对原有代码就有很高的侵入性,违反了开发-封闭原则。

  那么,在简单工厂这样的设计模式下,我没有没有办法做到对原有代码只进行扩展,不进行修改呢?答案是可以的,我们可把这些若干个产品的实体类的全路径记录到配置文件当中,然后读取配置文件拿到这些类的全路径,通过反射技术来创建对应的实例对象,下面我们用代码来实现:

首先,先来个配置文件,记录各产品的全路径名:

A=com.jack.course.interview.patterns.factory.ProductA
B=com.jack.course.interview.patterns.factory.ProductB
C=com.jack.course.interview.patterns.factory.ProductC
config.properties

然后修改 ProductFactory 工厂类的逻辑,通过传入的字符串来获取对应类的全路径名,从而来创建其对应的实例对象

public class ProductFactorySenior {

  private ProductFactorySenior() {
    //
  }

  private static class ClassHolder {
    private static final ProductFactorySenior INSTANCE = new ProductFactorySenior();
  }

  public static ProductFactorySenior getInstance() {
    return ClassHolder.INSTANCE;
  }

  // 通过类的全路径名创建实例对象
  public Product createProduct(String className) {
    try {
      String classFullName = getClassName(className);
      Class<?> clazz = Class.forName(classFullName);
      return (Product) clazz.newInstance();
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("不存在的类");
    } catch (IOException e) {
      throw new IllegalArgumentException("读取文件失败");
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("实例化异常");
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("异常访问,可能此类没有对构造器的访问权限");
    }
  }

  // 读取配置文件,获取类的全路径名
  private String getClassName(String className) throws IOException {
    Properties prop = new Properties();
    prop.load(ProductFactorySenior.class.getClassLoader().getResourceAsStream("config.properties"));
    return prop.getProperty(className);
  }
}
ProductFactorySenior

再来看一下客户端的调用

public class App {

  public static void main(String[] args) {

    Product productA = ProductFactorySenior.getInstance().createProduct("A");
    productA.create();
    Product productB = ProductFactorySenior.getInstance().createProduct("B");
    productB.create();
    Product productC = ProductFactorySenior.getInstance().createProduct("C");
    productC.create();
  }
}

  可以看到:客户端的调用没有任何变化,当需要增加产品时,只需要在本地新增对应的产品类后,在配置文件中指明类的全路径名,不需要对 ProductFactorySenior工厂类做任何的修改就可以完成,这样就符合软件开发过程中的开放-封闭原则,对原有代码没有了任何的侵入。

二、工厂方法模式

  工厂方法(Factory Method)的定义是一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂接口不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

  可以看到,上图中左边是工厂抽象和实现体系,右边是产品抽象和实现体系,工厂体系依赖于产品体系,每一个工厂负责创建一个产品,这样就省区了简单工厂中最初版本中工厂类中的分支判断,由客户端去决定实例化哪一个工厂类,从而创建对应的产品实例。下面我们来看一下代码实现:

还是一个产品接口和两个具体产品

public interface Product {

  void create();
}
Product
public class ProductA implements Product {

  @Override
  public void create() {
    System.out.println("create product A success!");
  }
}
ProductA
public class ProductB implements Product {

  @Override
  public void create() {
    System.out.println("create product B success!");
  }
}
ProductB

再来是一个工厂接口和两个创建指定产品的工厂

public interface Factory {
  
  Product createProduct();
}
Factory
public class FactoryA implements Factory {

  @Override
  public Product createProduct() {
    return new ProductA();
  }
}
FactoryA
public class FactoryB implements Factory {

  @Override
  public Product createProduct() {
    return new ProductB();
  }
}
FactoryB

再来看一下客户端的调用

public class App {
  public static void main(String[] args) {
    Factory factory = new FactoryA();
    Product productA = factory.createProduct();
    productA.create();

    factory = new FactoryB();
    Product productB = factory.createProduct();
    productB.create();
  }
}

  这种方法能够解决一定代码的扩展性问题,即:新增新的产品是不会对之前的代码进行修改。但是,新增新的产品时,还需要新增具体的工厂类,虽然能够解决简单工厂模式初版中工厂类中的分支判断问题,但是仔细分析我们发现,工厂方法实现时,这种判断交给了客户端,由客户端来决定实例化哪个工厂,从而创建对应的产品实例。

  所以,比较好的方式还是简单工厂模式最后介绍的使用反射技术来实现的方法,既能解决代码的扩展性,又不用增加新的工厂类,客户端的调用也不需要更改。

三、抽象工厂模式

  抽象工厂(Abstract Factory)的定义是:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

  我们分析一下抽象工厂的定义,我们要创建一个接口,这个接口就是指的工厂接口 Creator,而一组相关或者相互依赖的对象,则是指 ProductA 和 ProductB 以及它们具体的实现类,而上面又提到说不是返回具体的类,所以我们返回的应该是接口或者抽象类,那么在上述类图当中,则是指的 ProductA 和 ProductB 接口。下面,再来看看代码实现:

接口 ProductA 和它两个实现类

public interface ProductA {
  void createA();
}
interface ProductA
public class ProductA1 implements ProductA {

  @Override
  public void createA() {
    System.out.println("create product A1 success!");
  }
}
class ProductA1
public class ProductA2 implements ProductA {

  @Override
  public void createA() {
    System.out.println("create product A2 success!");
  }
}
class ProductA2

接口 ProductB 和它两个实现类

public interface ProductB {
  void createB();
}
interface ProductB
public class ProductB1 implements ProductB {

  @Override
  public void createB() {
    System.out.println("create product B1 success!");
  }
}
class ProductB1
public class ProductB2 implements ProductB {

  @Override
  public void createB() {
    System.out.println("create product B2 success!");
  }
}
class ProductB2

再来是工厂接口和工厂实现类

public interface Factory {

  ProductA createProductA();
  ProductB createProductB();
}
interface Factory
public class Factory1 implements Factory{

  @Override
  public ProductA createProductA() {
    return new ProductA1();
  }

  @Override
  public ProductB createProductB() {
    return new ProductB1();
  }
}
class Factory1
public class Factory2 implements Factory{

  @Override
  public ProductA createProductA() {
    return new ProductA2();
  }

  @Override
  public ProductB createProductB() {
    return new ProductB2();
  }
}
class Factory2

最后来看一下客户端调用

public class App {

  public static void main(String[] args) {
    Factory factory1 = new Factory1();
    ProductA productA = factory1.createProductA();
    ProductB productB = factory1.createProductB();
    productA.createA();
    productB.createB();

    Factory factory2 = new Factory2();
    ProductA productA2 = factory2.createProductA();
    ProductB productB2 = factory2.createProductB();
    productA2.createA();
    productB2.createB();
  }
}

那么,抽象工厂这么设计,有什么优点和缺点呢?

优点:

  • 易于交换产品系列,从客户端调用和结果来看,我们只是将工厂由 Factory1 变化为 Factory2,运行的结果就由 A1、B1 变化为 A2、B2 了。所以我们只需要改变具体的工厂就能得到不同的产品配置。
  • 它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口来操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。

缺点:

  • 抽象工厂能够很方便的切换产品配置,但是如果要再增加一个产品系列,比如说再增加一个 C 类产品,除了本来就要增加的接口 ProductC 以及它的实现类 ProductC1 和 ProductC2 以外,我们还需要更改工厂系列 Factory、Factory1 和 Factory2,加上对应的方法。

那么,分析到这里,我们发现抽象工厂方法对代码的扩展非常不友好,需要改动的地方很多,有没有什么解决办法呢?

我们可以用简单工厂的思路来解决,先把工厂系列 Factory、Factory1 和 Factory2去掉,增加一个具体的工厂类 ProductFactory,通过获取配置文件当中的全路径,使用反射技术创建对应的实例对象,具体代码如下:

首先,是配置文件

A1=com.jack.course.interview.patterns.factory.abstractfactory.ProductA1
A2=com.jack.course.interview.patterns.factory.abstractfactory.ProductA2
B1=com.jack.course.interview.patterns.factory.abstractfactory.ProductB1
B2=com.jack.course.interview.patterns.factory.abstractfactory.ProductB2
config.properties

然后是工厂类

public class ProductFactory {

  private ProductFactory() {
    //
  }

  private static class ClassHolder {
      private static final ProductFactory INSTANCE = new ProductFactory();
  }

  public static ProductFactory getInstance() {
      return ClassHolder.INSTANCE;
  }

  // 通过类的全路径名创建实例对象
  public Object createProduct(String className) {
    try {
      String classFullName = getClassName(className);
      Class<?> clazz = Class.forName(classFullName);
      return clazz.newInstance();
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("不存在的类");
    } catch (IOException e) {
      throw new IllegalArgumentException("读取文件失败");
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("实例化异常");
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("异常访问,可能此类没有对构造器的访问权限");
    }
  }

  // 读取配置文件,获取类的全路径名
  private String getClassName(String className) throws IOException {
    Properties prop = new Properties();
    prop.load(ProductFactory.class.getClassLoader().getResourceAsStream("config.properties"));
    return prop.getProperty(className);
  }
}
ProductFactory

通过反射创建的是 Object 对象,因为我们不知道创建的是哪一个类的实例对象,所以这里用 Object对象,避免写多个重复的方法

最后看一下客户端调用

public class App {
  public static void main(String[] args) {
    ProductA a1 = (ProductA) ProductFactory.getInstance().createProduct("A1");
    a1.createA();
    ProductA a2 = (ProductA) ProductFactory.getInstance().createProduct("A2");
    a2.createA();
    ProductB b1 = (ProductB) ProductFactory.getInstance().createProduct("B1");
    b1.createB();
    ProductB b2 = (ProductB) ProductFactory.getInstance().createProduct("B2");
    b2.createB();
  }
}

因为 createProduct() 返回的是 Object 对象,所以这里需要用对应的类型转换一下。

如果我们需要新增的产品类型和具体的产品,只需要再增加产品接口和具体的实现类外,在配置文件中指明具体实现类的全路径,客户端就可以正常创建了。

所以说,这种通过反射技术实现的工厂方法应该是工厂方法中最好的实现了。

四、工厂模式使用场景

  • 开源框架的实践
    • Spring中通过getBean("xxx")获取Bean
    • ActiveMQ建立连接
  • 自动化框架的实践

posted on 2020-05-31 18:23  破解孤独  阅读(197)  评论(0编辑  收藏  举报

导航