面試官問,你在開發中有用過什麼設計模式嗎?我懵了
設計模式不應該停留於理論,跟具體業務結合,它才會變得更香~
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技術棧電子書、各個大廠面試題。
&n