1、簡單工廠模式
??簡單工廠模式又叫靜態(tài)工廠方法模式(Static Factory Method Pattern),是通過專門定義一個類來負(fù)責(zé)創(chuàng)建其他類的實例,被創(chuàng)建的實例通常都具有共同的父類。
??一個簡單的實例:要求實現(xiàn)一個計算機控制臺程序,要求輸入數(shù)的運算結(jié)果。最原始的解決方法如下:
/**
* @Description:這里使用的是最基本的實現(xiàn),并沒有體現(xiàn)出面向?qū)ο蟮木幊趟枷?,代碼的擴展性差,甚至連除數(shù)可能為0的情況也沒有考慮
*/
public static void main(String[] args) {
scanner = new Scanner(System.in);
System.out.print("請輸入第一個數(shù)字:");
int firstNum = scanner.nextInt();
System.out.print("請輸入第二個數(shù)字:");
int secondNum = scanner.nextInt();
System.out.print("請輸入運算符:");
String operation = scanner.next();
if(operation.equals("+")) {
System.out.println("result:" + (firstNum + secondNum));
} else if(operation.equals("-")) {
System.out.println("result:" + (firstNum - secondNum));
} else if(operation.equals("*")) {
System.out.println("result:" + (firstNum * secondNum));
} else if(operation.equals("/")){
System.out.println("result:" + (firstNum / secondNum));
}
}
??上面的寫法實現(xiàn)雖然簡單,但是卻沒有面向?qū)ο蟮奶匦?,代碼拓展性差,甚至沒有考慮除數(shù)可能為0的特殊情況。
??在面向?qū)ο缶幊陶Z言中,一切都是對象,所以上面運算符號也應(yīng)當(dāng)作對象來處理。因此我們首先建立一個運算接口,所有其他的運算都封裝成類,并實現(xiàn)該運算接口。
/**
* @Description: 定義一個運算接口,將所有的運算符號都封裝成類,并實現(xiàn)本接口
* @author: zxt
* @time: 2018年7月6日 上午10:24:13
*/
public interface Operation {
public double getResult(double firstNum, double secondNum);
}
public class AddOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum + secondNum;
}
}
public class SubOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum - secondNum;
}
}
public class MulOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum * secondNum;
}
}
public class DivOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
if(secondNum == 0) {
try {
throw new Exception("除數(shù)不能為0!");
} catch (Exception e) {
e.printStackTrace();
}
}
return firstNum / secondNum;
}
}
??現(xiàn)在的問題的是,如何根據(jù)不同的情況創(chuàng)建不同的對象,這里就可以使用簡單工廠模式來實現(xiàn)了,客戶端只需要提供運算符,工廠類會判斷并生成相應(yīng)的運算類:
/**
* @Description: 簡單工廠模式:通過一個工廠類,根據(jù)情況創(chuàng)建不同的對象
* @author: zxt
* @time: 2018年7月6日 上午10:50:15
*/
public class OperationFactory {
/**
* @Description:根據(jù)運算符得到具體的運算類
* @param operationStr
*/
public static Operation getOperation(String operationStr) {
Operation result = null;
switch(operationStr) {
case "+":
result = new AddOperation();
break;
case "-":
result = new SubOperation();
break;
case "*":
result = new MulOperation();
break;
case "/":
result = new DivOperation();
break;
}
return result;
}
}
// 客戶端調(diào)用
Operation oper = OperationFactory.getOperation(operation);
double result = oper.getResult(firstNum, secondNum);
System.out.println(result);
??簡單工廠將對象的創(chuàng)建過程進(jìn)行了封裝,用戶不需要知道具體的創(chuàng)建過程,只需要調(diào)用工廠類獲取對象即可。
??這種簡單工廠的寫法是通過switch-case來判斷對象創(chuàng)建過程的。在實際使用過程中,違背了開放-關(guān)閉原則(例如,當(dāng)需要擴展一個新的運算符之后,簡單工廠創(chuàng)建的對象也必須多一種,這就需要修改原來的代碼了,違背了對修改關(guān)閉的原則),當(dāng)然有些情況下可以通過反射調(diào)用來彌補這種不足。
2、工廠方法模式
??簡單工廠模式的最大優(yōu)點在于工廠類中包含了必要的邏輯判斷,根據(jù)客戶端的選擇條件動態(tài)實例化相關(guān)的類,對于客戶端來說,去除了與具體產(chǎn)品的依賴。但是每擴展一個類時,都需要改變工廠類里的方法,這就違背了開放-封閉原則。于是工廠方法模式來了:
??工廠方法模式(Factory Method),定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類,工廠方法使一個類的實例化延遲到其子類。 繼續(xù)上一個計算器的例子,簡單工廠模式由工廠類直接生成相應(yīng)的運算類對象,判斷的邏輯在工廠類中,而工廠方法模式的實現(xiàn)則是定義一個工廠接口,然后每個運算類都對應(yīng)一個工廠類來創(chuàng)建,然后在客戶端判斷使用哪個工廠類來創(chuàng)建運算類。
/**
* @Description: 工廠的接口
* @author: zxt
* @time: 2019年2月21日 下午2:49:43
*/
public interface IFactory {
public Operation createOperation();
}
/**
* @Description: 加法類工廠
*/
public class AddFactory implements IFactory {
@Override
public AddOperation createOperation() {
return new AddOperation();
}
}
/**
* @Description: 減法類工廠
*/
public class SubFactory implements IFactory {
@Override
public SubOperation createOperation() {
return new SubOperation();
}
}
/**
* @Description: 乘法類工廠
*/
public class MulFactory implements IFactory {
@Override
public MulOperation createOperation() {
return new MulOperation();
}
}
/**
* @Description: 除法類工廠
*/
public class DivFactory implements IFactory {
@Override
public DivOperation createOperation() {
return new DivOperation();
}
}
??工廠方法模式實現(xiàn)時,客戶端需要決定實例化哪一個工廠來實現(xiàn)運算類,選擇判斷的問題還是存在的,也就是說,工廠方法把簡單工廠的內(nèi)部邏輯判斷移到了客戶端代碼來進(jìn)行。你想要加功能,本來是改工廠類的,而現(xiàn)在是修改客戶端。
/**
* @Description: 實現(xiàn)一個簡單的計算器功能,使用工廠方法模式
* @author: zxt
* @time: 2018年7月6日 上午10:11:50
*/
public class Computer {
private static Scanner scanner;
public static void main(String[] args) {
scanner = new Scanner(System.in);
System.out.print("請輸入第一個數(shù)字:");
int firstNum = scanner.nextInt();
System.out.print("請輸入第二個數(shù)字:");
int secondNum = scanner.nextInt();
System.out.print("請輸入運算符:");
String operation = scanner.next();
IFactory operFactory = null;
if(operation.equals("+")) {
operFactory = new AddFactory();
} else if(operation.equals("-")) {
operFactory = new SubFactory();
} else if(operation.equals("*")) {
operFactory = new MulFactory();
} else if(operation.equals("/")){
operFactory = new DivFactory();
}
Operation oper = operFactory.createOperation();
double result = oper.getResult(firstNum, secondNum);
System.out.println("result = " + result);
}
}
??增加新功能時,工廠方法模式比簡單工廠模式修改的代碼量更小,工廠方法克服了簡單工廠違背開放封閉原則的缺點,又保持了封裝對象創(chuàng)建過程的優(yōu)點。但是工廠方法的缺點就是每加一個產(chǎn)品,就需要加一個產(chǎn)品工廠的類,增加了額外的開發(fā)量。當(dāng)然這兩種模式都還不是最佳的做法。
3、抽象工廠模式
??抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和最具一般性的一種形態(tài)。抽象工廠模式是指當(dāng)有多個抽象角色時,使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個接口,使客戶端在不必指定產(chǎn)品的具體的情況下,創(chuàng)建多個產(chǎn)品族中的產(chǎn)品對象。根據(jù)里氏替換原則,任何接受父類型的地方,都應(yīng)當(dāng)能夠接受子類型。因此,實際上系統(tǒng)所需要的,僅僅是類型與這些抽象產(chǎn)品角色相同的一些實例,而不是這些抽象產(chǎn)品的實例。換言之,也就是這些抽象產(chǎn)品的具體子類的實例。工廠類負(fù)責(zé)創(chuàng)建抽象產(chǎn)品的具體子類的實例。
??抽象工廠模式(Abstract Factory):提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定他們具體的類。
??實例場景:對數(shù)據(jù)庫(各種不同的數(shù)據(jù)庫)中的表進(jìn)行修改,此時,使用工廠模式結(jié)構(gòu)圖如下:

1、User表的定義:
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2、定義一個對User表進(jìn)行操作的接口:
/**
* @Description: 對User類操作的接口
* @author: zxt
* @time: 2019年2月24日 下午7:00:20
*/
public interface IUser {
void insert(User user);
User getUser(int id);
}
3、實現(xiàn)Sql Server數(shù)據(jù)庫對User表的操作:
/**
* @Description: SQL Server數(shù)據(jù)庫中對User表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:04:39
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 SQL Server 中給 User 表增加一條記錄!");
}
@Override
public User getUser(int id) {
System.out.println("在 SQL Server 中根據(jù)ID得到 User 表的一條記錄!");
return null;
}
}
實現(xiàn)Oracle數(shù)據(jù)庫對User表的操作:
/**
* @Description: Oracle數(shù)據(jù)庫中對User表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:05:07
*/
public class OracleUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 Oracle 中給 User 表增加一條記錄!");
}
@Override
public User getUser(int id) {
System.out.println("在 Oracle 中根據(jù)ID得到 User 表的一條記錄!");
return null;
}
}
4、定義一個抽象工廠接口,用于生成對User表的操作的對象:
/**
* @Description: 得到對User表操作的IUser對象的抽象工廠接口
* @author: zxt
* @time: 2019年2月24日 下午7:06:37
*/
public interface IFactory {
public IUser createUser();
}
5、SQLServerFactory工廠用于生成操作Sql Server數(shù)據(jù)庫的SqlServerUser對象:
public class SQLServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
}
OracleFactory工廠用于生成操作Oracle數(shù)據(jù)庫的OracleUser對象:
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
}
6、客戶端的使用:
public class FactoryMethodTest {
public static void main(String[] args) {
User user = new User();
// 若要改成Oracle數(shù)據(jù)庫,只需要將這句改成OracleFactory即可
IFactory ifactory = new SQLServerFactory();
IUser iu = ifactory.createUser();
iu.insert(user);
iu.getUser(1);
}
}
??到此為止,工廠模式都可以很好的解決,由于多態(tài)的關(guān)系,IFactory在聲明對象之前都不知道在訪問哪個數(shù)據(jù)庫,卻可以在運行時很好的完成任務(wù),這就是業(yè)務(wù)邏輯與數(shù)據(jù)訪問的解耦。
??但是,當(dāng)數(shù)據(jù)庫中不止一個表的時候該怎么解決問題呢,此時就可以引入抽象工廠模式了,結(jié)構(gòu)圖如下:

例如增加了部門表Department:
public class Department {
private int id;
private String deptName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
則增加相應(yīng)的對Department表操作的接口:
public interface IDepartment {
public void insert(Department department);
public Department getDepartment(int id);
}
實現(xiàn)Sql Server數(shù)據(jù)庫對Department表的操作:
/**
* @Description: SQL Server數(shù)據(jù)庫中對Department表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:04:39
*/
public class SqlServerDepartment implements IDepartment {
@Override
public void insert(Department user) {
System.out.println("在 SQL Server 中給 Department 表增加一條記錄!");
}
@Override
public Department getDepartment(int id) {
System.out.println("在 SQL Server 中根據(jù)ID得到 Department 表的一條記錄!");
return null;
}
}
實現(xiàn)Oracle數(shù)據(jù)庫對Department表的操作:
/**
* @Description: Oracle數(shù)據(jù)庫中對Department表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:05:07
*/
public class OracleDepartment implements IDepartment {
@Override
public void insert(Department user) {
System.out.println("在 Oracle 中給 Department 表增加一條記錄!");
}
@Override
public Department getDepartment(int id) {
System.out.println("在 Oracle 中根據(jù)ID得到 Department 表的一條記錄!");
return null;
}
}
IFactory抽象工廠中增加生成對Department表操作的對象:
/**
* @Description: 得到對User表操作的IUser對象的抽象工廠接口
* @author: zxt
* @time: 2019年2月24日 下午7:06:37
*/
public interface IFactory {
public IUser createUser();
public IDepartment createDepartment();
}
SQLServerFactory工廠增加生成操作Sql Server數(shù)據(jù)庫的SqlServerDepartment對象:
public class SQLServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
OracleFactory工廠增加生成操作Oracle數(shù)據(jù)庫的OracleDepartment對象:
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
@Override
public IDepartment createDepartment() {
return new OracleDepartment();
}
}
客戶端的使用:
public class AbstractFactoryTest {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
// 若要改成SQL Server數(shù)據(jù)庫,只需要將這句改成SqlServerFactory即可
IFactory ifactory = new OracleFactory();
IUser iu = ifactory.createUser();
iu.insert(user);
iu.getUser(1);
IDepartment id = ifactory.createDepartment();
id.insert(department);
id.getDepartment(1);
}
}

??所以抽象工廠與工廠方法模式的區(qū)別在于:抽象工廠是可以生產(chǎn)多個產(chǎn)品的,例如OracleFactory 里可以生產(chǎn) OracleUser以及 OracleDepartment兩個產(chǎn)品,而這兩個產(chǎn)品又是屬于一個系列的,因為它們都是屬于Oracle數(shù)據(jù)庫的表。而工廠方法模式則只能生產(chǎn)一個產(chǎn)品,例如之前的 OracleFactory里就只可以生產(chǎn)一個 OracleUser產(chǎn)品。

??抽象工廠模式的優(yōu)缺點:
??優(yōu)點:
??1、抽象工廠模式最大的好處是易于交換產(chǎn)品系列,由于具體工廠類,例如 IFactory factory = new OracleFactory(); 在一個應(yīng)用中只需要在初始化的時候出現(xiàn)一次,這就使得改變一個應(yīng)用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產(chǎn)品配置。不管是任何人的設(shè)計都無法去完全防止需求的更改,或者項目的維護(hù),那么我們的理想便是讓改動變得最小、最容易。
??2、抽象工廠模式的另一個好處就是它讓具體的創(chuàng)建實例過程與客戶端分離,客戶端是通過它們的抽象接口操作實例,產(chǎn)品實現(xiàn)類的具體類名也被具體的工廠實現(xiàn)類分離,不會出現(xiàn)在客戶端代碼中。就像我們上面的例子,客戶端只認(rèn)識IUser和IDepartment,至于它是Sql Server里的表還是Oracle里的表就不知道了。
??缺點:
??1、如果你的需求來自增加功能,比如增加Department表,就有點太煩了。首先需要增加IDepartment,SQLServerDepartment,OracleDepartment。然后我們還要去修改工廠類:IFactory,SQLServerFactory,OracleFactory才可以實現(xiàn),需要修改三個類,實在是有點麻煩。
??2、還有就是,客戶端程序肯定不止一個,每次都需要聲明IFactory factory = new OracleFactory(),如果有100個調(diào)用數(shù)據(jù)庫的類,就需要更改100次IFactory factory = new OracleFactory()。
3.1、抽象工廠模式的改進(jìn)1(簡單工廠+抽象工廠)
??我們將IFactory,SQLServerFactory,OracleFactory三個工廠類都拋棄掉,取而代之的是一個簡單工廠類EasyFactory,如下:
public class EasyFactory {
private static String db = "SqlServer";
// private static String db = "Oracle";
public static IUser createUser() {
IUser result = null;
switch (db) {
case "SqlServer":
result = new SqlServerUser();
break;
case "Oracle":
result = new OracleUser();
break;
}
return result;
}
public static IDepartment createDepartment() {
IDepartment result = null;
switch (db) {
case "SqlServer":
result = new SqlServerDepartment();
break;
case "Oracle":
result = new OracleDepartment();
break;
}
return result;
}
}
客戶端:
public class EasyClient {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
// 直接得到實際的數(shù)據(jù)庫訪問實例,而不存在任何依賴
IUser userOperation = EasyFactory.createUser();
userOperation.getUser(1);
userOperation.insert(user);
// 直接得到實際的數(shù)據(jù)庫訪問實例,而不存在任何依賴
IDepartment departmentOperation = EasyFactory.createDepartment();
departmentOperation.insert(department);
departmentOperation.getDepartment(1);
}
}
??由于事先在簡單工廠類里設(shè)置好了db的值,所以簡單工廠的方法都不需要由客戶端來輸入?yún)?shù),這樣在客戶端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createDepartment(); 方法來獲得具體的數(shù)據(jù)庫訪問類的實例,客戶端代碼上沒有出現(xiàn)任何一個 SqlServer 或 Oracle 的字樣,達(dá)到了解耦的目的,客戶端已經(jīng)不再受改動數(shù)據(jù)庫訪問的影響了。
3.2、抽象工廠的改進(jìn)2(反射+簡單工廠)
??使用反射的話,我們就可以不需要使用switch,因為使用switch的話,我添加一個Mysql數(shù)據(jù)庫的話,又要switch的話又需要添加case條件。
??我們可以根據(jù)選擇的數(shù)據(jù)庫名稱,如“mysql”,利用反射技術(shù)自動的獲得所需要的實例:
public class EasyFactoryReflect {
private static String packName = "com.zxt.abstractfactory";
private static String sqlName = "Oracle";
public static IUser createUser() throws Exception {
String className = packName + "." + sqlName + "User";
return (IUser) Class.forName(className).newInstance();
}
public static IDepartment createLogin() throws Exception {
String className = packName + "." + sqlName + "Department";
return (IDepartment) Class.forName(className).newInstance();
}
}
??以上我們使用簡單工廠模式設(shè)計的代碼中,是用一個字符串類型的db變量來存儲數(shù)據(jù)庫名稱的,所以變量的值到底是 SqlServer 還是 Oracle,完全可以由事先設(shè)置的那個db變量來決定,而我們又可以通過反射來去獲取實例,這樣就可以去除switch語句了。
3.3、抽象工廠的改進(jìn)3(反射+配置文件+簡單工廠)
??在使用反射之后,我們還是需要進(jìn)EasyFactory中修改數(shù)據(jù)庫類型,還不是完全符合開-閉原則。我們可以通過配置文件來達(dá)到目的,每次通過讀取配置文件來知道我們應(yīng)該使用哪種數(shù)據(jù)庫。
??如下是一個json類型的配置文件,也可以使用xml類型的配置文件:
{
"packName": " com.zxt.abstractfactory",
"DB": "Oracle"
}
??之后就可以通過這個配置文件去找需要加載的類是哪一個。我們通過反射機制+配置文件+簡單工廠模式解決了數(shù)據(jù)庫訪問時的可維護(hù)、可擴展的問題。