面試官問,你在開發中有用過什麼設計模式嗎?我懵了

設計模式不應該停留於理論,跟具體業務結合,它纔會變得更香~

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)業務迭代過程中。當你的代碼隨着業務的調整需要不斷動刀改動,破壞了設計模式的七大原則,尤其是開閉原則,那麼,你就該去考慮考慮使用設計模式了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章