1. 程式人生 > 實用技巧 >設計模式系列:模板方法模式

設計模式系列:模板方法模式

一、問題引入

在面向物件程式設計中,經常會遇到這種情況:知道了一個過程的關鍵步驟,且確定了這些步驟的執行順序,但某些步驟的具體實現未知,或者說某些步驟的實現與具體的環境相關。這種情況下就可以使用模板模式。

例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對工作人員評分等,其中取號、排隊和對銀行工作人員評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以另外在子類中實現。

這樣的例子在生活中還有很多,例如,一個人每天會起床、吃飯、做事、睡覺等,其中“做事”的內容每天可能不同。我們把這些規定了流程或格式的例項定義成模板。

二、模式定義

以下介紹的模板方法模式將解決以上類似的問題。

1、模板模式在書中定義:
  定義一個操作中演算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變演算法的結構即可重定義該演算法的某些特定步驟。 
  通俗點的理解就是:完成一件事情,有固定的數個步驟,但是每個步驟根據物件的不同,而實現細節不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去呼叫其每個步驟的實現方法。每個步驟的具體實現,由子類完成。

2.、模板模式的類圖:

 

 抽象父類(AbstractClass):實現了模板方法,定義了演算法的骨架。

 具體類(ConcreteClass):實現抽象類中的抽象方法,即不同的物件的具體實現細節。

三、例項說明

1、去銀行辦業務步驟:(1)取號 (2)辦具體業務 (3)服務評價打分。這三個步驟就可以抽取到父類中進行定義,(1)取號和(3)服務打分是相同操作,可以直接在父類中實現,然後(2)辦具體的業務各不相同需要在子類中實現。

//銀行業務抽象父類
abstract class BankBusiness
{
    public void TemplateMethod() //模板方法
    {
        takeNum();
        doBusiness();          
        rate();
    }  
    
    public void takeNum() //
取號 { log.info("使用者取號"); } public void rate() //打分 { log.info("使用者打分"); } public abstract void doBusiness(); //辦業務 } //開戶業務子類 class Account extends BankBusiness { public void doBusiness() { log.info("使用者開戶"); } } //轉賬業務子類 class Transfer extends BankBusiness { public void doBusiness() { log.info("使用者轉賬"); } } //測試呼叫類 public class TemplateMethodPattern { public static void main(String[] args) { BankBusiness bb = new Account(); bb.TemplateMethod(); } }

2、模板方法模式在實際專案中的應用。物聯網閘道器收到裝置端傳送的資料請求後,會根據不同的請求型別進行不同的業務處理。業務處理步驟都是(1)讀取解析請求資料、(2)處理具體的請求、(3)響應請求、(4)連線心跳檢測。其中,(1)、(3)、(4)是所有請求的通用步驟,而(2)則需要根據不同的請求進行不同的處理。例項程式碼如下:

//業務抽象父類
@slfj
public abstract class AbstractMqttHandler extends SimpleChannelInboundHandler<MqttMessage>{

    @Autowired 
    private INetworkAlertService iNetworkAlertService;

    //建立連線,解析資料
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception {
        log.info("連線建立,名稱【{}】",ctx.name());
        MqttFixedHeader mqttFixedHeader = msg.fixedHeader();
        
        //呼叫doMessage方法,根據不同的請求進行不同的處理
        Optional.ofNullable(mqttFixedHeader)
                .ifPresent( t -> doMessage(ctx, msg));
    }

    //處理請求
    protected abstract void doMessage(ChannelHandlerContext ctx, MqttMessage mqttMessage);

    //連線關閉
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        Channel channel = ctx.channel();
        String deviceSn = ChannelUtil.getDeviceSn(channel);
        if(StringUtils.isBlank(deviceSn))
            return;

        MqttChannel mqttChannel = MqttServerHandlerDispather.mqttChannels.get(deviceSn);
        log.info("MqttChannler = 【{}】, 登入狀態【{}】", JsonUtil.toJson(mqttChannel), mqttChannel.isLogin());
        if(mqttChannel != null && mqttChannel.isCleanSession()) {
            MqttServerHandlerDispather.mqttChannels.remove(deviceSn);
            log.info("====關閉連線,裝置號為:==="+deviceSn);
        }
    }
}


//業務實現子類
public class MqttServerHandlerDispather extends AbstractMqttHandler {

    @Override
    protected void doMessage(ChannelHandlerContext ctx, MqttMessage mqttMessage){

        Channel channel = ctx.channel();

        MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader();

        //如果是連線請求
        if(mqttFixedHeader.messageType().equals(MqttMessageType.CONNECT)) {
            if(protocolProcess.getConnect().processConnect(channel,mqttMessage)) {
                ......
            }
        }
        
        MqttChannel mqttChannel = mqttChannels.get(ChannelUtil.getDeviceSn(channel));

        //判斷channel和登入狀態
        if(mqttChannel != null && mqttChannel.isLogin()){
            //如果是處理業務
            switch (mqttFixedHeader.messageType()) {
            
                case PUBLISH:
                    log.info("==================case PUBLISH======進入處理mq訊息流程==============");
                    protocolProcess.getPublish().processPublish(channel,mqttMessage);
                    break;
                    
                case PINGREQ:
                    log.info("==================case PINGREQ===回覆確認========");
                    protocolProcess.getPingReq().processPingReq(channel,mqttMessage);
                    break;
                    
                case DISCONNECT:
                    log.info("==================case DISCONNECT======解除連線=============");
                    protocolProcess.getDisConnect().processDisConnect(channel,mqttMessage);
                    break;
                
                ......
            }
        }
    }
}