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設計模式系列

相關文章
相關標籤/搜索