看完這篇,code review 誰敢噴你代碼寫的爛?懟回去!

  我將常用的軟件設計模式,做了彙總,目錄如下:

(考慮到內容篇幅較大,爲了便於大家閱讀,將軟件設計模式系列(共 23 個)拆分成四篇文章,每篇文章講解六個設計模式,採用不同的顏色區分,便於快速消化記憶)

本文,主要講解模板模式策略模式狀態模式觀察者模式訪問者模式備忘錄模式

1、模板模式

定義:

定義一個操作中的算法的骨架,將一些步驟延遲到子類中。模板模式使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

優點:1、封裝不變部分,擴展可變部分。2、提取公共代碼,便於維護。3、行爲由父類控制,子類實現。缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,維護成本高。

核心思路:

代碼示例:

public TradeFlowActionResult execute(TradeFlowActionParam param, Map context) throws ServiceException {
    try {    // 業務邏輯校驗
        this.validateBusinessLogic(param, context);
    } catch (ServiceException ex) {
        sendGoodsLog.info("SendGoodsAction->validateBusinessLogic got exception. param is " + param, ex);
        throw ex;
    } catch (RuntimeException ex) {
        sendGoodsLog.info("SendGoodsAction->validateBusinessLogic got runtime exception. param is " + param, ex);
        throw ex;
    }
    try {
        // 賣家發貨業務邏輯
        this.sendGoods(param, context);
    } catch (ServiceException ex) {
        sendGoodsLog.info("SendGoodsAction->sendGoods got exception. param is " + param, ex);
        throw ex;
    } catch (RuntimeException ex) {
        sendGoodsLog.info("SendGoodsAction->sendGoods got runtime exception. param is " + param, ex);
        throw ex;
    }
    try {
        // 補充業務(結果不影響核心業務)
        this.addition(param, context);
    } catch (ServiceException ex) {
        sendGoodsLog.info("SendGoodsAction->addition got exception. param is " + param, ex);
        throw ex;
    } catch (RuntimeException ex) {
        sendGoodsLog.info("SendGoodsAction->addition got runtime exception. param is " + param, ex);
        throw ex;
    }
    // 處理結果
    return null;
}

上面提到的三個抽象方法(業務邏輯校驗、賣家發貨業務邏輯、補充業務)都是在子類中實現的。即控制了主流程結構,又不失靈活性,可以讓使用者在其基礎上定製開發。如果有新的業務玩法進來,原來的流程滿足不了需求,我們可以基於模板類編寫新的子類。

適用場景:

2、策略模式

定義:

定義一系列算法,並將每種算法分別放入獨立的類中,以使算法的對象能夠相互替換。

由客戶端自己決定在什麼樣的情況下使用哪些具體的策略。

核心思路:

代碼示例:

/**
 * @author 微信公衆號:微觀技術
 */
public interface PromotionStrategy {
    // 活動類型
    String promotionType();
    // 活動優惠
    int recommand(String productId);
}

public class FullSendPromotion implements PromotionStrategy {
    @Override
    public String promotionType() {
        return "FullSend";
    }

    @Override
    public int recommand(String productId) {
        System.out.println("參加滿送活動");
        return 0;
    }
}

public class FullReducePromotion implements PromotionStrategy {
    @Override
    public String promotionType() {
        return "FullReduce";
    }

    @Override
    public int recommand(String productId) {
        System.out.println("參加滿減活動");
        return 0;
    }
}

@author 微信公衆號:微觀技術
public class Context {

    private static List<PromotionStrategy> promotionStrategyList = new ArrayList<>();
    static {
        promotionStrategyList.add(new FullReducePromotion());
        promotionStrategyList.add(new FullSendPromotion());
    }
    public void recommand(String promotionType, String productId) {
        PromotionStrategy promotionStrategy = null;
        // 找到對應的策略類
        for (PromotionStrategy temp : promotionStrategyList) {
            if (temp.promotionType().equals(promotionType)) {
                promotionStrategy = temp;
            }
        }
        // 策略子類調用
        promotionStrategy.recommand(productId);
    }

首先,定義活動策略的接口類PromotionStrategy,定義接口方法,每一種具體的策略算法都要實現該接口,FullSendPromotionFullReducePromotion

Context負責存儲和使用策略,彙集了所有的策略子類。並根據傳入的參數,匹配到具體的策略子類,然後調用recommand方法,處理具體的業務。

使用策略的好處:

適用場景:

3、狀態模式

定義:

一種行爲設計模式,讓你能在一個對象的內部狀態變化時改變其行爲,使其看上去就像改變了自身所屬的類一樣。

通過定義一系列狀態的變化來控制行爲的變化。以電商爲例,用戶的訂單會經歷以下這些狀態:已下單、已付款、已發貨、派送中、待取件、已簽收、交易成功、交易關閉等狀態。

核心思路:

代碼示例:

/**
 * @author 微信公衆號:微觀技術
 * 訂單狀態,接口定義(擴展實現若干不同狀態的子類)
 */
public interface OrderState {
    void handle(OrderContext context);
}

// 下單
public class MakeOrderState implements OrderState {
    public static MakeOrderState instance = new MakeOrderState();
    @Override
    public void handle(OrderContext context) {

        System.out.println("1、創建訂單");
        context.setCurrentOrderState(PayOrderState.instance);
    }
}

// 付款、確認收貨,實現類相似,這裏省略。。。。

// 訂單上下文
public class OrderContext {
    private OrderState currentOrderState;

    public OrderContext(OrderState currentOrderState) {
        if (currentOrderState == null) {
            this.currentOrderState = new MakeOrderState();
        } else {
            this.currentOrderState = currentOrderState;
        }
    }

    public void setCurrentOrderState(OrderState currentOrderState) {
        this.currentOrderState = currentOrderState;
    }

    public void execute() {
        currentOrderState.handle(this);
    }
}

運行結果:

1、創建訂單
2、支付寶付款
3、確認收到貨物

狀態模式設計的核心點在於找到合適的抽象狀態以及狀態之間的轉移關係,通過改變狀態來達到改變行爲的目的。

適用場景:

4、觀察者模式

定義:

也稱 發佈 - 訂閱模式,是一種通知機制,當一個對象改變狀態時,它的所有依賴項都會自動得到通知和更新。讓發送通知的一方(被觀察者)和接收通知的一方(觀察者,支持多個)能彼此分離,互不影響,該模式在軟件開發中非常流行。

像我們常見的KafkaRocketMQ等消息中間件都是採用這種架構模式,還有SpringApplicationEvent 異步事件驅動,有很好的低耦合特性。

類似其他叫法:

核心思路:

代碼示例:

/**
 * @author 微信公衆號:微觀技術
 * <p>
 * 被觀察者
 */
public interface Publisher {
    void add(Observer observer);
    void remove(Observer observer);
    void notify(Object object);
}

public class PublisherImpl implements Publisher {
    private List<Observer> observerList = new ArrayList<>();
    @Override
    public void add(Observer observer) {
        observerList.add(observer);
    }
    @Override
    public void remove(Observer observer) {
        observerList.remove(observer);
    }
    @Override
    public void notify(Object object) {
        System.out.println("創建訂單,併發送通知事件");
        observerList.stream().forEach(t -> t.handle());
    }
}

// 觀察者接口
public interface Observer {
    public void handle();
}

// 觀察者子類
public class EmailObserver implements Observer {
    @Override
    public void handle() {
        System.out.println("發送郵件通知!");
    }
}

觀察者模式的核心精髓:被觀察者定義了一個通知列表,收集了所有的觀察者對象,當被觀察者需要發出通知時,就會通知這個列表的所有觀察者

適用場景:

5、訪問者模式

定義:

訪問者模式是一種將操作與對象結構分離的軟件設計模式,允許在運行時將一個或多個操作應用於一組對象。

核心思路:

代碼示例:

/**
 * @author 微信公衆號:微觀技術
 * 定義行爲動作
 */
public interface RouterVisitor {
    // 交換機,發送數據
    void sendData(JhjRouterElement jhjRouterElement);
    // 路由器,發送數據
    void sendData(LyqRouterElement lyqRouterElement);
}

public class LinuxRouterVisitor implements RouterVisitor {
    @Override
    public void sendData(JhjRouterElement jhjRouterElement) {
        System.out.println("Linux 環境下,交換機發送數據");
    }

    @Override
    public void sendData(LyqRouterElement lyqRouterElement) {
        System.out.println("Linux 環境下,路由器發送數據");
    }
}

public class WindowRouterVisitor implements RouterVisitor {
   // 省略。。。
}


/**
 * @author 微信公衆號:微觀技術
 * <p>
 * 路由元素
 */
public interface RouterElement {
    // 發送數據
    void sendData(RouterVisitor routerVisitor);
}
public class JhjRouterElement implements RouterElement {

    @Override
    public void sendData(RouterVisitor routerVisitor) {
        System.out.println("交換機開始工作。。。");
        routerVisitor.sendData(this);
    }
}
public class LyqRouterElement implements RouterElement {
 // 省略。。。
}

適用場景:

6、備忘錄模式

定義:

也叫快照模式,用來存儲另外一個對象內部狀態的快照,便於以後可以恢復。

核心思路:

代碼示例:

/**
 * @author 微信公衆號:微觀技術
 * 原始對象
 */
@Data
public class Originator {
    private Long id;
    private String productName;
    private String picture;
    // 創建快照
    public Memento bak() {
        return new Memento(id, productName, picture);
    }
    //恢復
    public void restore(Memento m) {
        this.id = m.getId();
        this.productName = m.getProductName();
        this.picture = m.getPicture();
    }
}


// 快照
@Data
@AllArgsConstructor
public class Memento {
    private Long id;
    private String productName;
    private String picture;
}

適用場景:

寫在最後

設計模式很多人都學習過,但項目實戰時總是暈暈乎乎,原因在於沒有了解其核心是什麼,底層邏輯是什麼,《設計模式:可複用面向對象的基礎》有講過,

在設計中思考什麼應該變化,並封裝會發生變化的概念。

軟件架構的精髓:找到變化,封裝變化。

業務千變萬化,沒有固定的編碼答案,千萬不要硬套設計模式。無論選擇哪一種設計模式,儘量要能滿足SOLID原則,自我 review 是否滿足業務的持續擴展性。有句話說的好,“不論白貓黑貓,能抓老鼠就是好貓。”

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/epk2QDWZswz0VOsz9ACeug