1、簡單工廠模式
??簡單工廠模式又叫靜態(tài)工廠方法模式(Static Factory Method Pattern)婶芭,是通過專門定義一個(gè)類來負(fù)責(zé)創(chuàng)建其他類的實(shí)例东臀,被創(chuàng)建的實(shí)例通常都具有共同的父類。
??一個(gè)簡單的實(shí)例:要求實(shí)現(xiàn)一個(gè)計(jì)算機(jī)控制臺程序犀农,要求輸入數(shù)的運(yùn)算結(jié)果惰赋。最原始的解決方法如下:
/**
* @Description:這里使用的是最基本的實(shí)現(xiàn),并沒有體現(xiàn)出面向?qū)ο蟮木幊趟枷牒巧冢a的擴(kuò)展性差赁濒,甚至連除數(shù)可能為0的情況也沒有考慮
*/
public static void main(String[] args) {
scanner = new Scanner(System.in);
System.out.print("請輸入第一個(gè)數(shù)字:");
int firstNum = scanner.nextInt();
System.out.print("請輸入第二個(gè)數(shù)字:");
int secondNum = scanner.nextInt();
System.out.print("請輸入運(yùn)算符:");
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));
}
}
??上面的寫法實(shí)現(xiàn)雖然簡單,但是卻沒有面向?qū)ο蟮奶匦悦虾Γa拓展性差拒炎,甚至沒有考慮除數(shù)可能為0的特殊情況。
??在面向?qū)ο缶幊陶Z言中挨务,一切都是對象击你,所以上面運(yùn)算符號也應(yīng)當(dāng)作對象來處理。因此我們首先建立一個(gè)運(yùn)算接口谎柄,所有其他的運(yùn)算都封裝成類丁侄,并實(shí)現(xiàn)該運(yùn)算接口。
/**
* @Description: 定義一個(gè)運(yùn)算接口朝巫,將所有的運(yùn)算符號都封裝成類鸿摇,并實(shí)現(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)建不同的對象拙吉,這里就可以使用簡單工廠模式來實(shí)現(xiàn)了潮孽,客戶端只需要提供運(yùn)算符,工廠類會(huì)判斷并生成相應(yīng)的運(yùn)算類:
/**
* @Description: 簡單工廠模式:通過一個(gè)工廠類庐镐,根據(jù)情況創(chuàng)建不同的對象
* @author: zxt
* @time: 2018年7月6日 上午10:50:15
*/
public class OperationFactory {
/**
* @Description:根據(jù)運(yùn)算符得到具體的運(yùn)算類
* @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)建過程的怠堪。在實(shí)際使用過程中,違背了開放-關(guān)閉原則(例如名眉,當(dāng)需要擴(kuò)展一個(gè)新的運(yùn)算符之后粟矿,簡單工廠創(chuàng)建的對象也必須多一種,這就需要修改原來的代碼了损拢,違背了對修改關(guān)閉的原則)陌粹,當(dāng)然有些情況下可以通過反射調(diào)用來彌補(bǔ)這種不足。
2福压、工廠方法模式
??簡單工廠模式的最大優(yōu)點(diǎn)在于工廠類中包含了必要的邏輯判斷掏秩,根據(jù)客戶端的選擇條件動(dòng)態(tài)實(shí)例化相關(guān)的類,對于客戶端來說荆姆,去除了與具體產(chǎn)品的依賴蒙幻。但是每擴(kuò)展一個(gè)類時(shí),都需要改變工廠類里的方法胆筒,這就違背了開放-封閉原則邮破。于是工廠方法模式來了:
??工廠方法模式(Factory Method),定義一個(gè)用于創(chuàng)建對象的接口仆救,讓子類決定實(shí)例化哪一個(gè)類抒和,工廠方法使一個(gè)類的實(shí)例化延遲到其子類。 繼續(xù)上一個(gè)計(jì)算器的例子彤蔽,簡單工廠模式由工廠類直接生成相應(yīng)的運(yùn)算類對象摧莽,判斷的邏輯在工廠類中,而工廠方法模式的實(shí)現(xiàn)則是定義一個(gè)工廠接口顿痪,然后每個(gè)運(yùn)算類都對應(yīng)一個(gè)工廠類來創(chuàng)建镊辕,然后在客戶端判斷使用哪個(gè)工廠類來創(chuàng)建運(yùn)算類。
/**
* @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();
}
}
??工廠方法模式實(shí)現(xiàn)時(shí)员魏,客戶端需要決定實(shí)例化哪一個(gè)工廠來實(shí)現(xiàn)運(yùn)算類丑蛤,選擇判斷的問題還是存在的叠聋,也就是說撕阎,工廠方法把簡單工廠的內(nèi)部邏輯判斷移到了客戶端代碼來進(jìn)行。你想要加功能碌补,本來是改工廠類的虏束,而現(xiàn)在是修改客戶端棉饶。
/**
* @Description: 實(shí)現(xiàn)一個(gè)簡單的計(jì)算器功能,使用工廠方法模式
* @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("請輸入第一個(gè)數(shù)字:");
int firstNum = scanner.nextInt();
System.out.print("請輸入第二個(gè)數(shù)字:");
int secondNum = scanner.nextInt();
System.out.print("請輸入運(yùn)算符:");
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);
}
}
??增加新功能時(shí)镇匀,工廠方法模式比簡單工廠模式修改的代碼量更小照藻,工廠方法克服了簡單工廠違背開放封閉原則的缺點(diǎn),又保持了封裝對象創(chuàng)建過程的優(yōu)點(diǎn)汗侵。但是工廠方法的缺點(diǎn)就是每加一個(gè)產(chǎn)品幸缕,就需要加一個(gè)產(chǎn)品工廠的類,增加了額外的開發(fā)量晰韵。當(dāng)然這兩種模式都還不是最佳的做法发乔。
3、抽象工廠模式
??抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和最具一般性的一種形態(tài)雪猪。抽象工廠模式是指當(dāng)有多個(gè)抽象角色時(shí)栏尚,使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個(gè)接口只恨,使客戶端在不必指定產(chǎn)品的具體的情況下译仗,創(chuàng)建多個(gè)產(chǎn)品族中的產(chǎn)品對象。根據(jù)里氏替換原則官觅,任何接受父類型的地方纵菌,都應(yīng)當(dāng)能夠接受子類型。因此缰猴,實(shí)際上系統(tǒng)所需要的产艾,僅僅是類型與這些抽象產(chǎn)品角色相同的一些實(shí)例,而不是這些抽象產(chǎn)品的實(shí)例滑绒。換言之闷堡,也就是這些抽象產(chǎn)品的具體子類的實(shí)例。工廠類負(fù)責(zé)創(chuàng)建抽象產(chǎn)品的具體子類的實(shí)例疑故。
??抽象工廠模式(Abstract Factory):提供一個(gè)創(chuàng)建一系列相關(guān)或相互依賴對象的接口杠览,而無需指定他們具體的類。
??實(shí)例場景:對數(shù)據(jù)庫(各種不同的數(shù)據(jù)庫)中的表進(jìn)行修改纵势,此時(shí)踱阿,使用工廠模式結(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钦铁、定義一個(gè)對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软舌、實(shí)現(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;
}
}
實(shí)現(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、定義一個(gè)抽象工廠接口超营,用于生成對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在聲明對象之前都不知道在訪問哪個(gè)數(shù)據(jù)庫窝革,卻可以在運(yùn)行時(shí)很好的完成任務(wù),這就是業(yè)務(wù)邏輯與數(shù)據(jù)訪問的解耦吕座。
??但是聊闯,當(dāng)數(shù)據(jù)庫中不止一個(gè)表的時(shí)候該怎么解決問題呢,此時(shí)就可以引入抽象工廠模式了米诉,結(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);
}
實(shí)現(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;
}
}
實(shí)現(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)多個(gè)產(chǎn)品的惊橱,例如OracleFactory 里可以生產(chǎn) OracleUser以及 OracleDepartment兩個(gè)產(chǎn)品蚪腐,而這兩個(gè)產(chǎn)品又是屬于一個(gè)系列的,因?yàn)樗鼈兌际菍儆贠racle數(shù)據(jù)庫的表税朴。而工廠方法模式則只能生產(chǎn)一個(gè)產(chǎn)品回季,例如之前的 OracleFactory里就只可以生產(chǎn)一個(gè) OracleUser產(chǎn)品。
??抽象工廠模式的優(yōu)缺點(diǎn):
??優(yōu)點(diǎn):
??1正林、抽象工廠模式最大的好處是易于交換產(chǎn)品系列泡一,由于具體工廠類,例如 IFactory factory = new OracleFactory(); 在一個(gè)應(yīng)用中只需要在初始化的時(shí)候出現(xiàn)一次觅廓,這就使得改變一個(gè)應(yīng)用的具體工廠變得非常容易鼻忠,它只需要改變具體工廠即可使用不同的產(chǎn)品配置。不管是任何人的設(shè)計(jì)都無法去完全防止需求的更改杈绸,或者項(xiàng)目的維護(hù)帖蔓,那么我們的理想便是讓改動(dòng)變得最小、最容易瞳脓。
??2塑娇、抽象工廠模式的另一個(gè)好處就是它讓具體的創(chuàng)建實(shí)例過程與客戶端分離,客戶端是通過它們的抽象接口操作實(shí)例劫侧,產(chǎn)品實(shí)現(xiàn)類的具體類名也被具體的工廠實(shí)現(xiàn)類分離埋酬,不會(huì)出現(xiàn)在客戶端代碼中。就像我們上面的例子,客戶端只認(rèn)識IUser和IDepartment奇瘦,至于它是Sql Server里的表還是Oracle里的表就不知道了。
??缺點(diǎn):
??1劲弦、如果你的需求來自增加功能耳标,比如增加Department表,就有點(diǎn)太煩了邑跪。首先需要增加IDepartment次坡,SQLServerDepartment,OracleDepartment画畅。然后我們還要去修改工廠類:IFactory砸琅,SQLServerFactory,OracleFactory才可以實(shí)現(xiàn)轴踱,需要修改三個(gè)類症脂,實(shí)在是有點(diǎn)麻煩。
??2淫僻、還有就是诱篷,客戶端程序肯定不止一個(gè),每次都需要聲明IFactory factory = new OracleFactory()雳灵,如果有100個(gè)調(diào)用數(shù)據(jù)庫的類棕所,就需要更改100次IFactory factory = new OracleFactory()。
3.1悯辙、抽象工廠模式的改進(jìn)1(簡單工廠+抽象工廠)
??我們將IFactory琳省,SQLServerFactory,OracleFactory三個(gè)工廠類都拋棄掉躲撰,取而代之的是一個(gè)簡單工廠類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í)際的數(shù)據(jù)庫訪問實(shí)例,而不存在任何依賴
IUser userOperation = EasyFactory.createUser();
userOperation.getUser(1);
userOperation.insert(user);
// 直接得到實(shí)際的數(shù)據(jù)庫訪問實(shí)例拢蛋,而不存在任何依賴
IDepartment departmentOperation = EasyFactory.createDepartment();
departmentOperation.insert(department);
departmentOperation.getDepartment(1);
}
}
??由于事先在簡單工廠類里設(shè)置好了db的值坚踩,所以簡單工廠的方法都不需要由客戶端來輸入?yún)?shù),這樣在客戶端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createDepartment(); 方法來獲得具體的數(shù)據(jù)庫訪問類的實(shí)例瓤狐,客戶端代碼上沒有出現(xiàn)任何一個(gè) SqlServer 或 Oracle 的字樣瞬铸,達(dá)到了解耦的目的,客戶端已經(jīng)不再受改動(dòng)數(shù)據(jù)庫訪問的影響了础锐。
3.2嗓节、抽象工廠的改進(jìn)2(反射+簡單工廠)
??使用反射的話,我們就可以不需要使用switch皆警,因?yàn)槭褂胹witch的話拦宣,我添加一個(gè)Mysql數(shù)據(jù)庫的話,又要switch的話又需要添加case條件。
??我們可以根據(jù)選擇的數(shù)據(jù)庫名稱鸵隧,如“mysql”绸罗,利用反射技術(shù)自動(dòng)的獲得所需要的實(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è)計(jì)的代碼中,是用一個(gè)字符串類型的db變量來存儲(chǔ)數(shù)據(jù)庫名稱的豆瘫,所以變量的值到底是 SqlServer 還是 Oracle珊蟀,完全可以由事先設(shè)置的那個(gè)db變量來決定,而我們又可以通過反射來去獲取實(shí)例外驱,這樣就可以去除switch語句了育灸。
3.3、抽象工廠的改進(jìn)3(反射+配置文件+簡單工廠)
??在使用反射之后昵宇,我們還是需要進(jìn)EasyFactory中修改數(shù)據(jù)庫類型磅崭,還不是完全符合開-閉原則。我們可以通過配置文件來達(dá)到目的瓦哎,每次通過讀取配置文件來知道我們應(yīng)該使用哪種數(shù)據(jù)庫砸喻。
??如下是一個(gè)json類型的配置文件,也可以使用xml類型的配置文件:
{
"packName": " com.zxt.abstractfactory",
"DB": "Oracle"
}
??之后就可以通過這個(gè)配置文件去找需要加載的類是哪一個(gè)蒋譬。我們通過反射機(jī)制+配置文件+簡單工廠模式解決了數(shù)據(jù)庫訪問時(shí)的可維護(hù)恩够、可擴(kuò)展的問題。