設計模式不應該停留於理論,跟具體業務結合,它才會變得更香~
1.前言
設計模式我們多少都有些了解,但是往往也只是知道是什么。
在真實的業務場景中,你有用過什么設計模式來編寫更優雅的代碼嗎?
我們更多的是每天從產品經理那里接受到新需求后,就開始MVC一把梭,面向sql編程了。
我們習慣采用MVC架構,實時上是非常容易創建很多貧血對象模型,然后寫出過程式代碼。我們使用的對象,往往只是數據的載體,沒有任何邏輯行為。我們的設計過程,也是從ER圖開始,以數據為中心進行驅動設計。一個需求一個接口,從controller到service到dao,這樣日復一日的CRUD。
什么設計模式?根本不存在的!
今天,我們嘗試從常用設計模式(工廠模式、代理模式、模版模式)在CRUD中的可落地場景,希望能給大家帶來一些啟發。
2.理解設計模式
設計模式(Design pattern),不是前人憑空想象的,而是在長期的軟件設計實踐過程中,經過總結得到的。
使用設計模式是為了讓代碼具有可擴展性,實現高聚合、低耦合的特性。
世上本來沒有設計模式,寫代碼的人多了,便有了設計模式。
面向對象的設計模式有七大基本原則:
-
開閉原則(首要原則):要對擴展開放,對修改關閉
-
單一職責原則:實現類要職責單一
-
里氏代換原則:不要破壞繼承體系
-
依賴倒轉原則:面向接口編程
-
接口隔離原則:設計接口要精簡單一
-
合成/聚合復用原則:盡量先使用組合或者聚合來實現,其次才考慮使用繼承關系來實現
-
最少知識原則或者迪米特法則:降低耦合
過去,我們會去學習設計模式的理論,今天,我們嘗試從常用設計模式(工廠模式、代理模式、模版模式)在CRUD中的可落地場景,希望能給大家帶來一些實戰啟發。
3.設計模式實戰案例
3.1工廠模式
1)工廠模式介紹
工廠模式應該是我們最熟悉的設計模式了,很多框架都會有經典的xxxxFactory,然后通過xxxFactory.create來獲取對象。這里不詳細展開介紹,給出一個大家耳熟能詳的工廠模式類圖應該就能回憶起來了。

工廠模式的優點很明顯:
- 一個調用者想創建一個對象,只要知道其名稱就可以了。
- 擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。
- 屏蔽產品的具體實現,調用者只關心產品的接口。
那么,實際業務開發該怎么落地使用呢?
2)需求舉例
我們需要做一個HBase的管理系統,類似於MySQL的navicat或者workbench。
那么有一個很重要的模塊,就是實現HBase的增刪改查。
而開源的HBase-client已經提供了標准的增刪改查的api,我們如何集成到系統中呢?
3)簡單代碼
@RestController("/hbase/execute")
public class DemoController {
private HBaseExecuteService hbaseExecuteService;
public DemoController(ExecuteService executeService) {
this.hbaseExecuteService = executeService;
}
@PostMapping("/insert")
public void insertDate(InsertCondition insertCondition) {
hbaseExecuteService.insertDate(insertCondition);
}
@PostMapping("/update")
public void updateDate(UpdateCondition updateCondition) {
hbaseExecuteService.updateDate(updateCondition;
}
@PostMapping("/delete")
public void deleteDate(DeleteCondition deleteCondition) {
hbaseExecuteService.deleteData(deleteCondition);
}
@GetMapping("/select")
public Object selectDate(SelectCondition selectCondition) {
return hbaseExecuteService.seletData(selectCondition);
}
}
每次增加一個功能,都需要從controller到service寫一遍類似的操作。
還需要構建很多相關dto進行數據傳遞,里面會帶着很多重復的變量,比如表名、列名等查詢條件。
4)模式應用
抽象接口
public interface HBaseCommand {
/**
* 執行命令
*/
ExecResult execute();
}
抽象類實現公共配置
public class AbstractHBaseCommand implements HBaseCommand {
Configuration configuration;
AbstractHBaseCommand(ExecuteCondition executeCondition) {
this.configuration = getConfiguration(executeCondition.getResourceId());
}
private Configuration getConfiguration(String resourceId) {
Configuration conf = HBaseConfiguration.create();
//做一些配置相關事情
//。。。。
return conf;
}
@Override
public ExecResult execute() {
return null;
}
}
工廠類生產具體的命令
public class CommandFactory {
private ExecuteCondition executeCondition;
public CommandFactory(ExecuteCondition executeCondition) {
this.executeCondition = executeCondition;
}
public HBaseCommand create() {
HBaseCommand hBaseCommand;
switch (ExecuteTypeEnum.getTypeForName(executeCondition.getQueryType())) {
case Get:
return new GetCommand(executeCondition);
case Put:
return new PutCommand(executeCondition);
case Delete:
return new DeleteCommand(executeCondition);
}
return null;
}
}
一個執行接口,執行增刪改查多個命令
public class ExecuteController {
private ExecuteService executeService;
public ExecuteController(ExecuteService executeService) {
this.executeService = executeService;
}
@GetMapping
public Object exec(ExecuteCondition executeCondition) {
ExecResult execResult = executeService.execute(executeCondition);
return transform(execResult);
}
}
service調用工廠來創建具體的命令進行執行
@Service
public class ExecuteService {
public ExecResult execute(ExecuteCondition executeCondition) {
CommandFactory factory = new CommandFactory(executeCondition);
HBaseCommand command = factory.create();
return command.execute();
}
}
每次添加一個新的命令,只需要實現一個新的命令相關內容的類即可

3.2 代理模式
1) 模式介紹
代理模式也是大家非常熟悉的一種模式。
它給一個對象提供一個代理,並由代理對象控制對原對象的引用。它使得用戶不能直接與真正的目標對象通信。
代理對象類似於客戶端和目標對象之間的中介,能發揮比較多作用,比如擴展原對象的能力、做一些切面工作(打日志)、限制原對象的能力,同時也在一定程度上面減少了系統的耦合度。

2)需求舉例
現在已經有一個client對象,實現了若干方法。
現在,有兩個需求:
- 希望這個對象的方法A不再被支持。
- 希望對這個client的所有方法的執行時間進行記錄。
3)簡單代碼
對原本的類進行修改
- 刪除方法A(不兼容改動,如果別人有引用,可能會編譯報錯)
- 對每個方法的前后埋點計算時間(業務入侵太大,代碼嚴重冗余)
4)模式應用
對於方法A的不再支持,其實有挺多辦法的,繼承或者靜態代理都可以。
靜態代理代碼:
public class ConnectionProxy implements Connection {
private Connection connection;
public ConnectionProxy(Connection connection) {
this.connection = connection;
}
@Override
public Admin getAdmin() throws IOException {
//拋出一個異常
throw new UnsupportedOperationException();
}
@Override
public boolean isClosed() {
return connection.isClosed();
}
@Override
public void abort(String why, Throwable e) {
connection.abort(why, e);
}
}
對於每個方法的前后計算埋點,可以使用動態代理進行實現。
public class TableProxy implements InvocationHandler {
private Object target;
public TableProxy(Object target) {
this.target = target;
}
/**
* 獲取被代理接口實例對象
*
* @param <T>
* @return
*/
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long current = System.currentTimeMillis();
Object invoke;
try {
invoke = method.invoke(target, args);
} catch (Throwable throwable) {
throw throwable;
}
long cost = System.currentTimeMillis() - current;
System.out.println("cost time: " + cost);
return invoke;
}
}
3.3 模板方法
1) 模式介紹
定義一個操作中的流程框架,而將一些流程中的具體步驟延遲到子類中實現。
模板方法使得子類可以不改變一個流程框架下,通過重定義該算法的某些特定步驟實現自定義的行為。
當然,最便利之處在於,我們可以保證一套完善的流程,使得不同子類明確知道自己需要實現哪些方法來完成這套流程。

2)需求舉例
其實模板方法是最容易理解的,也非常高效。
我們最常用模版方法的一類需求就是工單審批流。
具體來說,假如我們現在需要定義一套流程來實現一個工單審批,包含工單創建、審批操作、事件執行、消息通知等流程(實際上流程可能會更加復雜)。
而工單的對象非常多,可以是一個服務的申請、一個數據庫的變更申請、或者是一個權限申請。
3)簡單代碼
每個工單流程寫一套代碼。
- 重復工作多;
- 流程某些關鍵環節可能會缺失,比如事件執行以后忘記通知了。
4)模式應用
定義一個接口,里面包括了若干方法
public interface ChangeFlow {
void createOrder();
boolean approval();
boolean execute();
void notice();
}
在一個流程模版中,拼接各個方法,實現完整工作流
public class MainFlow {
public void mainFlow(ChangeFlow flow) {
flow.createOrder();
if (!flow.approval()){
System.out.println("抱歉,審批沒有通過");
}
if (!flow.execute()) {
System.out.println("抱歉,執行失敗");
}
flow.notice();
}
}
然后,可以在AbstractChangeFlow里面實現通用的方法,比如approval、notice,大家都是一樣的邏輯。
public class AbstractChangeFlow implements ChangeFlow {
@Override
public void createOrder() {
System.out.println("創建訂單");
}
@Override
public boolean approval() {
if (xxx) {
System.out.println("審批通過");
return true;
}
return false;
}
@Override
public boolean execute() {
//交給其他子類自己復寫
return true;
}
@Override
public void notice() {
System.out.println("notice");
}
}
最后,就根據具體的工單來實現自定義的excute()方法就行了,實現DbFlow、MainFlow就行了。

4.總結
學習設計模式最重要的就是要在業務開發過程中保持思考,在某一個特定的業務場景中,結合對業務場景的理解和領域模型的建立,才能體會到設計模式思想的精髓。
如果脫離具體的業務場景去學習或者談論設計模式,那是沒有意義的。
有人說,怎么區分設計模式和過度設計呢?
其實很簡單。
1)業務設計初期。如果非常熟悉業務特性,理解業務迭代方向,那么就可以做一些簡單的設計了。
2)業務迭代過程中。當你的代碼隨着業務的調整需要不斷動刀改動,破壞了設計模式的七大原則,尤其是開閉原則,那么,你就該去考慮考慮使用設計模式了。
看到這里了,原創不易,點個關注、點個贊吧,你最好看了~
知識碎片重新梳理,構建Java知識圖譜:https://github.com/saigu/JavaKnowledgeGraph(歷史文章查閱非常方便)
掃碼關注我的公眾號“阿丸筆記”,第一時間獲取最新更新。同時可以免費獲取海量Java技術棧電子書、各個大廠面試題。