Java设计模式(一) 简单工厂模式不简单

摘要:本文介绍了简单工厂模式的概念,优缺点,实现方式,以及结合Annotation和反射的改良方案(让简单工厂模式不简单)。同时介绍了简单工厂模式(未)遵循的OOP原则。最后给出了简单工厂模式在JDBC中的应用java



原创文章。同步自做者我的博客http://www.jasongj.com/design_pattern/simple_factorygit

简单工厂模式使用案例

有一种抽象产品——汽车(Car),同时有多种具体的子类产品,如BenzCar,BMWCar,LandRoverCar。类图以下
github

做为司机,若是要开其中一种车,好比BenzCar,最直接的作法是直接建立BenzCar的实例,并执行其drive方法,以下sql

package com.jasongj.client;

import com.jasongj.product.BenzCar;

public class Driver1 {

  public static void main(String[] args) {
    BenzCar car = new BenzCar();
    car.drive();
  }

}

此时若是要改成开Land Rover,则须要修改代码,建立Land Rover的实例并执行其drive方法。这也就意味着任什么时候候须要换一辆车开的时候,都必须修改客户端代码。数据库

一种稍微好点的方法是,经过读取配置文件,获取须要开的车,而后建立相应的实例并由父类Car的引用指向它,利用多态执行不一样车的drive方法。以下apache

package com.jasongj.client;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jasongj.product.BMWCar;
import com.jasongj.product.BenzCar;
import com.jasongj.product.Car;
import com.jasongj.product.LandRoverCar;

public class Driver2 {
  
  private static final Logger LOG = LoggerFactory.getLogger(Driver2.class);

  public static void main(String[] args) throws ConfigurationException {
    XMLConfiguration config = new XMLConfiguration("car.xml");
    String name = config.getString("driver2.name");
    Car car;

    switch (name) {
    case "Land Rover":
      car = new LandRoverCar();
      break;
    case "BMW":
      car = new BMWCar();
      break;
    case "Benz":
      car = new BenzCar();
      break;
    default:
      car = null;
      break;
    }
    LOG.info("Created car name is {}", name);
    car.drive();
  }

}

对于Car的使用方而言,只须要经过参数便可指定所须要Car的各种并获得其实例,同时不管使用哪一种Car,都不须要修改后续对Car的操做。至此,简单工厂模式的原型已经造成。若是把上述的逻辑判断封装到一个专门的类的静态方法中,则实现了简单工厂模式。工厂代码以下设计模式

package com.jasongj.factory;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jasongj.product.BMWCar;
import com.jasongj.product.BenzCar;
import com.jasongj.product.Car;
import com.jasongj.product.LandRoverCar;

public class CarFactory1 {
  
  private static final Logger LOG = LoggerFactory.getLogger(CarFactory1.class);

  public static Car newCar() {
    Car car = null;
    String name = null;
    try {
      XMLConfiguration config = new XMLConfiguration("car.xml");
      name = config.getString("factory1.name");
    } catch (ConfigurationException ex) {
      LOG.error("parse xml configuration file failed", ex);
    }

    switch (name) {
    case "Land Rover":
      car = new LandRoverCar();
      break;
    case "BMW":
      car = new BMWCar();
      break;
    case "Benz":
      car = new BenzCar();
      break;
    default:
      car = null;
      break;
    }
    LOG.info("Created car name is {}", name);
    return car;
  }

}

调用方代码以下工具

package com.jasongj.client;

import com.jasongj.factory.CarFactory1;
import com.jasongj.product.Car;

public class Driver3 {

  public static void main(String[] args) {
    Car car = CarFactory1.newCar();
    car.drive();
  }

}

与Driver2相比,全部的判断逻辑都封装在工厂(CarFactory1)当中,Driver3再也不须要关心Car的实例化,实现了对象的建立和使用的隔离。oop

固然,简单工厂模式并不要求必定要读配置文件来决定实例化哪一个类,能够把参数做为工厂静态方法的参数传入。优化

简单工厂模式进阶

使用反射实现扩展性

从Driver2和CarFactory1的实现中能够看到,当有新的车加入时,须要更新Driver2和CarFactory1的代码也实现对新车的支持。这就违反了开闭原则(Open-Close Principle)。能够利用反射(Reflection)解决该问题。

package com.jasongj.factory;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jasongj.product.Car;

public class CarFactory2 {
  
  private static final Logger LOG = LoggerFactory.getLogger(CarFactory2.class);

  public static Car newCar() {
    Car car = null;
    String name = null;
    try {
      XMLConfiguration config = new XMLConfiguration("car.xml");
      name = config.getString("factory2.class");
    } catch (ConfigurationException ex) {
      LOG.error("Parsing xml configuration file failed", ex);
    }
    
    try {
      car = (Car)Class.forName(name).newInstance();
      LOG.info("Created car class name is {}", name);
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
      LOG.error("Instantiate car {} failed", name);
    }
    return car;
  }

}

从上面代码中能够看到,以后若是须要引入新的Car,只须要在配置文件中指定该Car的完整类名(包括package名),CarFactory2便可经过反射将其实例化。实现了对扩展的开放,同时保证了对修改的关闭。熟悉Spring的读者应该会想到Spring IoC的实现。

注解让简单工厂模式不简单

上例中使用反射作到了对扩展开放,对修改关闭。但有些时候,使用类的全名不太方便,使用别名会更合适。例如Spring中每一个Bean都会有个ID,引用Bean时也会经过ID去引用。像Apache Nifi这样的数据流工具,在流程上使用了职责链模式,而对于单个Processor的建立则使用了工厂,对于用户自定义的Processor并不须要经过代码去注册,而是使用注解(为了更方便理解下面这段代码,请先阅读笔者另一篇文章《Java系列(一)Annotation(注解)》)。

下面就继续在上文案例的基础上使用注解升级简单工厂模式。

package com.jasongj.factory;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jasongj.annotation.Vehicle;
import com.jasongj.product.Car;

public class CarFactory3 {

  private static final Logger LOG = LoggerFactory.getLogger(CarFactory3.class);

  private static Map<String, Class> allCars;

  static {
    Reflections reflections = new Reflections("com.jasongj.product");
    Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(Vehicle.class);
    allCars = new ConcurrentHashMap<String, Class>();
    for (Class<?> classObject : annotatedClasses) {
      Vehicle vehicle = (Vehicle) classObject.getAnnotation(Vehicle.class);
      allCars.put(vehicle.type(), classObject);
    }
    allCars = Collections.unmodifiableMap(allCars);
  }

  public static Car newCar() {
    Car car = null;
    String type = null;
    try {
      XMLConfiguration config = new XMLConfiguration("car.xml");
      type = config.getString("factory3.type");
      LOG.info("car type is {}", type);
    } catch (ConfigurationException ex) {
      LOG.error("Parsing xml configuration file failed", ex);
    }

    if (allCars.containsKey(type)) {
      LOG.info("created car type is {}", type);
      try {
        car = (Car) allCars.get(type).newInstance();
      } catch (InstantiationException | IllegalAccessException ex) {
        LOG.error("Instantiate car failed", ex);
      }
    } else {
      LOG.error("specified car type {} does not exist", type);
    }
    return car;
  }

}

从上面代码中能够看到,该工厂会扫描全部被Vehicle注解的Car(每种Car都在注解中声明了本身的type,可做为该种Car的别名)而后创建起Car别名与具体Car的Class原映射。此时工厂的静态方法便可根据目标别名实例化对应的Car。

本文全部代码均可从做者GitHub下载.

简单工厂模式详解

简单工厂模式定义

简单工厂模式(Simple Factory Pattern)又叫静态工厂方法模式(Static FactoryMethod Pattern)。专门定义一个类(如上文中的CarFactory一、CarFactory二、CarFactory3)来负责建立其它类的实例,由它来决定实例化哪一个具体类,从而避免了在客户端代码中显式指定,实现了解耦。该类因为能够建立同一抽象类(或接口)下的不一样子类对象,就像一个工厂同样,所以被称为工厂类。

简单工厂模式类图

简单工厂模式类图以下所示

简单工厂模式角色划分

  • 工厂角色(如上文中的CarFactory1/2/3):这是简单工厂模式的核心,由它负责建立全部的类的内部逻辑。固然工厂类必须可以被外界调用,建立所须要的产品对象。通常而言,工厂类提供一个静态方法,外部程序经过该方法建立所需对象。
  • 抽象产品角色(如上文中的Car):简单工厂模式所建立的全部对象的父类。注意,这里的父类能够是接口也能够是抽象类,它负责描述所建立实例共有的公共接口。
  • 具体产品角色(如上文中的BMWCar,BenzCar,LandRoverCar):简单工厂所建立的具体实例对象,这些具体的产品每每都拥有共同的父类。

简单工厂模式优势

  • 工厂类是整个简单工厂模式的关键所在。它包含必要的判断逻辑,可以根据外界给定的信息(配置,或者参数),决定究竟应该建立哪一个具体类的对象。用户在使用时能够直接根据工厂类去建立所需的实例,而无需了解这些对象是如何建立以及如何组织的。有利于整个软件体系结构的优化。
  • 经过引入配置文件和反射,能够在不修改任何客户端代码的状况下更换和增长新的具体产品类,在必定程度上提升了系统的灵活性(如CarFactory2)。
  • 客户端无须知道所建立的具体产品类的类名,只须要知道具体产品类所对应的参数便可,对于一些复杂的类名,经过简单工厂模式能够减小使用者的记忆量(如CarFactory3)。

简单工厂模式缺点

  • 因为工厂类集中了全部实例的建立逻辑,这就直接致使一旦这个工厂出了问题,全部的客户端都会受到牵连。
  • 因为简单工厂模式的产品是基于一个共同的抽象类或者接口,这样一来,产品的种类增长的时候,即有不一样的产品接口或者抽象类的时候,工厂类就须要判断什么时候建立何种接口的产品,这就和建立何种种类的产品相互混淆在了一块儿,违背了单一职责原则,致使系统丧失灵活性和可维护性。
  • 正如上文提到的,通常状况下(如CarFactory1),简单工厂模式违背了“开放-关闭原则”,由于当咱们新增长一个产品的时候必须修改工厂类,相应的工厂类就须要从新编译一遍。但这一点能够利用反射(CarFactory3在本质上也是利用反射)在必定程度上解决(如CarFactory2)。
  • 使用反射可使简单工厂在必定条件下知足“开放-关闭原则”,但这仅限于产品类的构造及初始化相同的场景。对于各产品实例化或者初始化不一样的场景,很难利用反射知足“开放-关闭”原则。
  • 简单工厂模式因为使用了静态工厂方法,形成工厂角色没法造成基于继承的等级结构。这一点笔者持保留态度,由于继承不是目的,若是没有这样的需求,这一点彻底不算缺点,例如JDBC的DriverManager。

简单工厂模式与OOP原则

已遵循的原则

  • 依赖倒置原则
  • 迪米特法则
  • 里氏替换原则
  • 接口隔离原则

未遵循的原则

  • 开闭原则(如上文所述,利用配置文件+反射或者注解能够避免这一点)
  • 单一职责原则(工厂类即要负责逻辑判断又要负责实例建立)

简单工厂模式在JDK中的典型应用

简单工厂模式在JDK中最典型的应用要数JDBC了。能够把关系型数据库认为是一种抽象产品,各厂商提供的具体关系型数据库(MySQL,PostgreSQL,Oracle)则是具体产品。DriverManager是工厂类。应用程序经过JDBC接口使用关系型数据库时,并不须要关心具体使用的是哪一种数据库,而直接使用DriverManager的静态方法去获得该数据库的Connection。

package com.jasongj.client;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JDBC {
  
  private static final Logger LOG = LoggerFactory.getLogger(JDBC.class);

  public static void main(String[] args) {
    Connection conn = null;
    try {
      Class.forName("org.apache.hive.jdbc.HiveDriver");
      conn = DriverManager.getConnection("jdbc:hive2://127.0.0.1:10000/default");
      PreparedStatement ps = conn.prepareStatement("select count(*) from test.test");
      ps.execute();
    } catch (SQLException ex) {
      LOG.warn("Execute query failed", ex);
    } catch(ClassNotFoundException e) {
      LOG.warn("Load Hive driver failed", e);
    } finally {
      if(conn != null ){
        try {
          conn.close();
        } catch (SQLException e) {
          // NO-OPT
        }
      }
    }
  }
}

Java设计模式系列

相关文章
相关标签/搜索