首先,放上項目github地址: https://github.com/codethereforam/java design patterns, 我是用java實現的 一、前言 題目中的這三個設計模式屬於 ,作用是為了 抽象實例化過程 。 我之前學過這三個設計模式,但最近發現又無法釐清這三個的區別了 ...
首先,放上項目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 程傑