首先,放上項目github地址: https://github.com/codethereforam/java-design-patterns, 我是用java實現的
一、前言
題目中的這三個設計模式屬於創建型模式
,作用是為了抽象實例化過程。
我之前學過這三個設計模式,但最近發現又無法厘清這三個的區別了,為了避免下次又忘了,於是想動手記錄下來。
可能有同學有疑問,提前說一下,下面所展示的類圖
由IDEA
自帶插件UML Support
自動生成,而時序圖
由插件SequencePlugin
自動生成。如果有同學對類圖和時序圖還不了解,請先google自學一下。
下面我結合模擬場景總結一下這三個模式,具體代碼請點擊本文開頭的github鏈接。
二、簡單工廠
模擬場景:一個用戶管理系統,假設只有一張User表。本來用的mysql,但需求突然發生變化,現在要用Oralce,由於這兩個數據庫的SQL語句有些差別,需要重寫數據庫層面的代碼,現在要求系統可以靈活切換數據庫。
關鍵代碼:
public class UserDAOFactory {
//靜態工廠方法
public static UserDAO createUserDAO(String database) {
UserDAO userDAO = null;
switch (database) {
case "mysql":
userDAO = new UserDAOMysqlImpl();
break;
case "oracle":
userDAO = new UserDAOOracleImpl();
break;
default:
}
return userDAO;
}
}
分析:如果現在要改用SQL server
數據庫,需要添加一個UserDAOSqlserverImpl
,然后在UserDAOFactory
類的createUserDAO
方法中添加一個case,這顯然違背了開閉原則
。
-
特點
- 工廠類包含必要的邏輯判斷來選擇生產具體產品
- 用於生產單個產品
-
優點
- 去除客戶端與具體產品的依賴
-
缺點
- 添加產品需要修改工廠類,違背開閉原則
-
角色
- 抽象產品(UserDAO)
- 具體產品(UserDAOMysqlImpl & UserDAOOracleImpl)
- 工廠(UserDAOFactory)
三、工廠方法
模擬場景:和上述簡單工廠模擬場景一樣
分析:如果現在要改用SQL server
數據庫,則需添加一個UserDAOSqlserverImpl
和相應的工廠UserDAOFactorySqlserverImpl
,再更改Main中的實例化代碼,這滿足了開閉原則
,擴展很方便。但如果支持的數據庫一多,那工廠就會泛濫。
-
特點
- 一個產品對應一個工廠類
- 用於生產某種類型產品
-
優點
- 方便添加新產品
- 添加新產品只需添加相應工廠類,符合開閉原則
-
缺點
- 產品多時,工廠泛濫
-
角色
- 抽象產品(UserDAO)
- 具體產品(UserDAOMysqlImpl & UserDAOOracleImpl)
- 抽象工廠(UserDAOFactory)
- 具體工廠(UserDAOFactoryMysqlImpl & UserDAOFactoryOracleImpl)
四、抽象工廠
模擬場景:在之前的場景基礎上,如果系統本來還有一個日志表,是用來記錄日志的。
分析:如果現在要改用SQL server
數據庫,則需添加UserDAOSqlserverImpl
、LogDAOSqlserverImpl
和DAOFactorySqlserverImpl
,再更改Main中的實例化代碼。但如果現在要添加一個其他的表,那么就要改DAOFactory
接口和接口中方法的實現,要改動的地方太多。
-
特點
- 用於生產一系列產品
-
優點
- 易於改變工廠生產行為,產生新的產品系列
- 具體創建過程與客戶端分離,客戶端通過接口操縱實例(factory1.createUserDAO().add())
-
缺點
- 添加新產品,要修改抽象工廠接口、具體工廠,改動太多
-
角色
- 抽象產品(UserDAO & LogDAO)
- 具體產品(UserDAOMysqlImpl & UserDAOOracleImpl & LogDAOMysqlImpl & LogDAOOracleImpl)
- 抽象工廠(DAOFactory)
- 具體工廠(DAOFactoryMysqlImpl & DAOFactoryOracleImpl)
五、用簡單工廠改進抽象工廠
關鍵代碼(選取DataAccess
):
public UserDAO createUserDAO() {
UserDAO userDAO = null;
switch (database) {
case MYSQL:
userDAO = new UserDAOMysqlImpl();
break;
case ORACLE:
userDAO = new UserDAOOracleImpl();
break;
default:
}
return userDAO;
}
分析:與抽象工廠相比,該方法減少了三個類,添加了一個DataAccess
類,類的數量減少了,系統復雜性降低。如果現在要改用SQL server
數據庫,則需添加UserDAOSqlserverImpl
和LogDAOSqlserverImpl
,然后在DataAccess
類中的createUserDAO
方法和createLogDAO
方法分別添加一個case,這違背了開閉原則。
-
特點
- 用DataAccess取代抽象工廠和具體工廠
- DataAccess通過判斷控制生產行為
-
優點
- 減少類
-
缺點
- 添加新產品系列,要改動DataAccess中的switch-case
六、用反射改進抽象工廠
關鍵代碼(選取DataAccess
):
public static final String PACKAGE_NAME = DataAccess.class.getPackage().getName();
public UserDAO createUserDAO() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String className = PACKAGE_NAME + ".UserDAO" + database + "Impl";
return (UserDAO) Class.forName(className).newInstance();
}
分析:如果要換數據庫,則無需修改DataAccess
類中的代碼。如果要添加表,只需要添加一個抽象產品接口和兩個具體產品實現,然后在DataAccess
中添加一個create**
方法,擴展起來非常方便。
-
優點
- 減少類
- 解決抽象工廠添加產品改動較多的問題,方便擴展
-
可使用配置文件繼續完善
七、總結
如果你仔細看到這,你可能會覺得我例子舉的不恰當,哪里有系統只有一個表的呢,前面的場景直接考慮抽象工廠就行了。我承認我舉的例子有問題,之前寫代碼時沒有發現,應該是當時理解的還不夠深入。
簡單工廠和工廠方法的模擬場景應該改為:系統本來有一直表,但現在要添加表,而不是換數據庫。而抽象工廠的模擬場景應該改為:在上述的基礎上要換數據庫。如果你理解了三個模式,我想這兩個模擬場景你應該也知道怎么實現了。
本文的例子我參考了大話設計模式
,但其他代碼和文字是我自己的理解。如果有錯誤,望各位不吝賜教,在評論區指出。
八、參考資料
- 大話設計模式,by 程傑