摘要:本文介紹了簡單工廠模式的概念,優缺點,實現方式,以及結合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)來負責建立其它類的實例,由它來決定實例化哪一個具體類,從而避免了在客戶端代碼中顯式指定,實現瞭解耦。該類因爲能夠建立同一抽象類(或接口)下的不一樣子類對象,就像一個工廠同樣,所以被稱爲工廠類。
簡單工廠模式類圖以下所示
簡單工廠模式在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 } } } } }