原創(chuàng)文章,同步發(fā)自作者人個博客,轉(zhuǎn)載請務(wù)必將下面這段話置于文章開頭處。
本文轉(zhuǎn)發(fā)自Jason's Blog,原文鏈接 http://www.jasongj.com/design_pattern/simple_factory
簡單工廠模式使用案例
有一種抽象產(chǎn)品——汽車(Car),同時有多種具體的子類產(chǎn)品,如BenzCar,BMWCar,LandRoverCar。類圖如下
作為司機(jī),如果要開其中一種車,比如BenzCar,最直接的做法是直接創(chuàng)建BenzCar的實例,并執(zhí)行其drive方法,如下
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,則需要修改代碼,創(chuàng)建Land Rover的實例并執(zhí)行其drive方法。這也就意味著任何時候需要換一輛車開的時候,都必須修改客戶端代碼。
一種稍微好點(diǎn)的方法是,通過讀取配置文件,獲取需要開的車,然后創(chuàng)建相應(yīng)的實例并由父類Car的引用指向它,利用多態(tài)執(zhí)行不同車的drive方法。如下
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的使用方而言,只需要通過參數(shù)即可指定所需要Car的各類并得到其實例,同時無論使用哪種Car,都不需要修改后續(xù)對Car的操作。至此,簡單工廠模式的原型已經(jīng)形成。如果把上述的邏輯判斷封裝到一個專門的類的靜態(tài)方法中,則實現(xiàn)了簡單工廠模式。工廠代碼如下
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;
}
}
調(diào)用方代碼如下
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)當(dāng)中,Driver3不再需要關(guān)心Car的實例化,實現(xiàn)了對象的創(chuàng)建和使用的隔離。
當(dāng)然,簡單工廠模式并不要求一定要讀配置文件來決定實例化哪個類,可以把參數(shù)作為工廠靜態(tài)方法的參數(shù)傳入。
簡單工廠模式進(jìn)階
使用反射實現(xiàn)擴(kuò)展性
從Driver2和CarFactory1的實現(xiàn)中可以看到,當(dāng)有新的車加入時,需要更新Driver2和CarFactory1的代碼也實現(xiàn)對新車的支持。這就違反了開閉原則
(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即可通過反射將其實例化。實現(xiàn)了對擴(kuò)展的開放,同時保證了對修改的關(guān)閉。熟悉Spring的讀者應(yīng)該會想到Spring IoC的實現(xiàn)。
注解讓簡單工廠模式不簡單
上例中使用反射做到了對擴(kuò)展開放,對修改關(guān)閉。但有些時候,使用類的全名不太方便,使用別名會更合適。例如Spring中每個Bean都會有個ID,引用Bean時也會通過ID去引用。像Apache Nifi這樣的數(shù)據(jù)流工具,在流程上使用了職責(zé)鏈模式,而對于單個Processor的創(chuàng)建則使用了工廠,對于用戶自定義的Processor并不需要通過代碼去注冊,而是使用注解(為了更方便理解下面這段代碼,請先閱讀筆者另外一篇文章《Java系列(一)Annotation(注解)》)。
下面就繼續(xù)在上文案例的基礎(chǔ)上使用注解升級簡單工廠模式。
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原映射。此時工廠的靜態(tài)方法即可根據(jù)目標(biāo)別名實例化對應(yīng)的Car。
本文所有代碼都可從作者GitHub下載.
簡單工廠模式詳解
簡單工廠模式定義
簡單工廠模式(Simple Factory Pattern)又叫靜態(tài)工廠方法模式(Static FactoryMethod Pattern)。專門定義一個類(如上文中的CarFactory1、CarFactory2、CarFactory3)來負(fù)責(zé)創(chuàng)建其它類的實例,由它來決定實例化哪個具體類,從而避免了在客戶端代碼中顯式指定,實現(xiàn)了解耦。該類由于可以創(chuàng)建同一抽象類(或接口)下的不同子類對象,就像一個工廠一樣,因此被稱為工廠類。
簡單工廠模式類圖
簡單工廠模式類圖如下所示
簡單工廠模式角色劃分
- 工廠角色(如上文中的CarFactory1/2/3):這是簡單工廠模式的核心,由它負(fù)責(zé)創(chuàng)建所有的類的內(nèi)部邏輯。當(dāng)然工廠類必須能夠被外界調(diào)用,創(chuàng)建所需要的產(chǎn)品對象。一般而言,工廠類提供一個靜態(tài)方法,外部程序通過該方法創(chuàng)建所需對象。
- 抽象產(chǎn)品角色(如上文中的Car):簡單工廠模式所創(chuàng)建的所有對象的父類。注意,這里的父類可以是接口也可以是抽象類,它負(fù)責(zé)描述所創(chuàng)建實例共有的公共接口。
- 具體產(chǎn)品角色(如上文中的BMWCar,BenzCar,LandRoverCar):簡單工廠所創(chuàng)建的具體實例對象,這些具體的產(chǎn)品往往都擁有共同的父類。
簡單工廠模式優(yōu)點(diǎn)
- 工廠類是整個簡單工廠模式的關(guān)鍵所在。它包含必要的判斷邏輯,能夠根據(jù)外界給定的信息(配置,或者參數(shù)),決定究竟應(yīng)該創(chuàng)建哪個具體類的對象。用戶在使用時可以直接根據(jù)工廠類去創(chuàng)建所需的實例,而無需了解這些對象是如何創(chuàng)建以及如何組織的。有利于整個軟件體系結(jié)構(gòu)的優(yōu)化。
- 通過引入配置文件和反射,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產(chǎn)品類,在一定程度上提高了系統(tǒng)的靈活性(如CarFactory2)。
- 客戶端無須知道所創(chuàng)建的具體產(chǎn)品類的類名,只需要知道具體產(chǎn)品類所對應(yīng)的參數(shù)即可,對于一些復(fù)雜的類名,通過簡單工廠模式可以減少使用者的記憶量(如CarFactory3)。
簡單工廠模式缺點(diǎn)
- 由于工廠類集中了所有實例的創(chuàng)建邏輯,這就直接導(dǎo)致一旦這個工廠出了問題,所有的客戶端都會受到牽連。
- 由于簡單工廠模式的產(chǎn)品是基于一個共同的抽象類或者接口,這樣一來,產(chǎn)品的種類增加的時候,即有不同的產(chǎn)品接口或者抽象類的時候,工廠類就需要判斷何時創(chuàng)建何種接口的產(chǎn)品,這就和創(chuàng)建何種種類的產(chǎn)品相互混淆在了一起,違背了單一職責(zé)原則,導(dǎo)致系統(tǒng)喪失靈活性和可維護(hù)性。
- 正如上文提到的,一般情況下(如CarFactory1),簡單工廠模式違背了“開放-關(guān)閉原則”,因為當(dāng)我們新增加一個產(chǎn)品的時候必須修改工廠類,相應(yīng)的工廠類就需要重新編譯一遍。但這一點(diǎn)可以利用反射(CarFactory3在本質(zhì)上也是利用反射)在一定程度上解決(如CarFactory2)。
- 使用反射可以使簡單工廠在一定條件下滿足“開放-關(guān)閉原則”,但這僅限于產(chǎn)品類的構(gòu)造及初始化相同的場景。對于各產(chǎn)品實例化或者初始化不同的場景,很難利用反射滿足“開放-關(guān)閉”原則。
- 簡單工廠模式由于使用了靜態(tài)工廠方法,造成工廠角色無法形成基于繼承的等級結(jié)構(gòu)。這一點(diǎn)筆者持保留態(tài)度,因為繼承不是目的,如果沒有這樣的需求,這一點(diǎn)完全不算缺點(diǎn),例如JDBC的DriverManager。
簡單工廠模式與OOP原則
已遵循的原則
- 依賴倒置原則
- 迪米特法則
- 里氏替換原則
- 接口隔離原則
未遵循的原則
- 開閉原則(如上文所述,利用配置文件+反射或者注解可以避免這一點(diǎn))
- 單一職責(zé)原則(工廠類即要負(fù)責(zé)邏輯判斷又要負(fù)責(zé)實例創(chuàng)建)
簡單工廠模式在JDK中的典型應(yīng)用
簡單工廠模式在JDK中最典型的應(yīng)用要數(shù)JDBC了。可以把關(guān)系型數(shù)據(jù)庫認(rèn)為是一種抽象產(chǎn)品,各廠商提供的具體關(guān)系型數(shù)據(jù)庫(MySQL,PostgreSQL,Oracle)則是具體產(chǎn)品。DriverManager是工廠類。應(yīng)用程序通過JDBC接口使用關(guān)系型數(shù)據(jù)庫時,并不需要關(guān)心具體使用的是哪種數(shù)據(jù)庫,而直接使用DriverManager的靜態(tài)方法去得到該數(shù)據(jù)庫的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設(shè)計模式系列
- Java設(shè)計模式(一) 簡單工廠模式不簡單
- Java設(shè)計模式(二) 工廠方法模式
- Java設(shè)計模式(三) 抽象工廠模式
- Java設(shè)計模式(四) 觀察者模式
- Java設(shè)計模式(五) 組合模式
- Java設(shè)計模式(六) 代理模式 VS. 裝飾模式
- Java設(shè)計模式(七) Spring AOP JDK動態(tài)代理 vs. cglib
- Java設(shè)計模式(八) 適配器模式
- Java設(shè)計模式(九) 橋接模式
- Java設(shè)計模式(十) 你真的用對單例模式了嗎?
- Java設(shè)計模式(十一) 享元模式
- Java設(shè)計模式(十二) 策略模式
- Java設(shè)計模式(十三) 別人再問你設(shè)計模式,叫他看這篇文章