天天看點

原創 | 從抽象類開始,詳解責任鍊模式

原創 | 從抽象類開始,詳解責任鍊模式

大家好,歡迎大家閱讀周五的設計模式專題。

今天我們繼續介紹新的設計模式,和上次的鍊式模式不同,這一次要介紹的責任鍊模式不僅僅在Python當中有,在很多其他的語言當中同樣支援,比如Java。Python和Java當中的這個設計模式是完全一樣的,沒有任何差別。

和之前介紹的設計模式相比,今天介紹的設計模式同樣不難。我們非常容易想明白它的原理。

責任鍊

在我們日常的開發當中,經常會有上下遊的依賴,比如目前我們需要開發一份新的資料,需要用到上遊的某一份老資料才能生成。這時候需要我們寫一個接口,然後上遊也随之改造,把它們的新資料寫到我們這個接口來。

這就帶來了一個問題,也就是上下遊耦合的問題,如果下遊有一天變動了,上遊也需要随着變動。如果是一對一的可能還好,如果一個上遊存在多個下遊,那麼這會是一件很麻煩的事。每一個下遊變動了,上遊都需要調整。舉個最簡單的例子,比如下遊想要做一個AB測試,想要一半流量進入A機器,一半流量進入B機器。這都需要上遊配合。

這當然是很不合理的,在實際當中上遊的資料可能是另外一個團隊負責的,他們隻對資料品質負責,并不一定會願意配合。并且這樣的配合也會導緻效率低下。

為了解決這個問題,有了責任鍊的設計模式。我們把下遊的響應方存入一條鍊路當中,上遊在發送請求時不感覺下遊的接收情況以及相應細節。

說白了,用一句話來概括,就是用鍊路把下遊串起來。我們來看一張圖就明白了:

原創 | 從抽象類開始,詳解責任鍊模式

我們把圖中的采購人員看成是資料上遊的話,它隻管提出請求,具體響應的人他是不知道的。根據申請金額的不同,審批的人員也會不一樣。但是隻會有一個人審批,如果目前人員審批不了就會上報,也就是交由後面一個節點處理。不得不說是非常形象了。

代碼實作

那怎麼把下遊用一條鍊路串起來呢,其實也很簡單,我們可以利用抽象類來實作。比如說我們可以這樣:

class Handler:
    def __init__(self, next=None):
        self.next = next
        
    def handle(self, request):
        res = self.do_handle(request)
        if not res and self.next:
            self.next.handle(request)
            
 def do_handle(self, request):
        pass
           

複制

這裡的handler就是實作鍊式調用的關鍵,在handle當中會首先自己進行處理,如果處理失敗那麼會調用下一個handle來進行。Handle類中do_handle的方法是抽象方法,我們希望每一個繼承Handle的子類都能實作自己的do_handle邏輯。

是以如果是用Java來實作的話,會非常清晰,因為在Java當中有抽象類的相關概念。由于Python原生沒有抽象類的相關設定,是以會稍微隐晦一些,但是邏輯是也一樣的。

在我們代碼實作之前,我們先來介紹一個無關緊要的包。這個包就是Python當中的six,其實這個包也不算是無關緊要,通過它可以寫出相容Python2和Python3的代碼。但是實際上由于現在Python2已經快掃進曆史的垃圾堆了,是以有沒有這個包都沒有關系,這裡我們還是尊重原作者的代碼,保留了這麼一個包。我們用這個包作為注解,完成了抽象基類的建構。

抽象基類

首先,我們先來看這個抽象基類。在原生Python當中,其實是沒有抽象基類這麼一個概念的。抽象基類其實就是含有抽象方法的基類。

我們之前在介紹golang當中interface用法的時候曾經介紹過,我們來簡單回憶一下。拿比較常見的Java語言舉例。

比如說這是一個抽象基類:

abstract class Mammal {
    abstract void say();
    String name() {
        return "chengzhi";
    }
}
           

複制

對于這個類而言它是不可以直接建立執行個體的,因為我們可以看出來它有一個什麼也沒實作的方法say。這個方法前面加了一個關鍵字abstract即抽象的意思,表示這是一個抽象方法。類名的前面同樣加了這個關鍵字,表示這是一個抽象類。

對于抽象類我們不能直接建立它的執行個體,我們隻能建立實作了抽象類中抽象方法的子類的執行個體。

比如我們建立一個類實作它:

class Human extends Mammal{
    @override
 public void say() {
  System.out.println("perfect");
 }
}
           

複制

我們可以看出來Human這個類繼承了Mammal這個抽象類,并且實作了抽象類當中的方法say。是以我們可以建立它的執行個體。抽象類是實作多态的一種重要的方法,在強變量類型的語言當中,我們通過抽象類抽象出了多個子類共同的結構。這樣我們就可以通過父類的指針調用各種子類的對象了,這是非常友善的。

但是Python當中不支援,為什麼Python不支援呢?其實也很簡單,因為Python是弱變量類型語言。變量指派的時候對于類型根本沒有限制,我們可以做任何調用。

舉個例子,比如我們當下有A、B和C這三個類的執行個體。哪怕這三個類毫無關系,我們也可以用一個變量去分别接收這些執行個體然後調用同名的方法。

a = [A(), B(), C()]
for i in a:
    i.say()
           

複制

隻要A、B和C這三個類當中都有叫做say的方法,那麼上面這段邏輯就不會報錯。是以Python非常靈活,可以說是幾乎沒有限制,那麼當然也就沒有必要設計這麼一個抽象類的設定了。

但問題是我們開發的時候并不是這樣的,比如說我們在設計的時候希望能夠讓類之間有繼承的關系,并且希望繼承了某個父類的類都能實作某一個方法。由于Python原生沒有相關的設定,導緻我們很難在編碼的排查錯誤。比如說我們忘記實作了某個方法,由于Python沒有編譯的過程,我們隻有運作到報錯的地方才會遇到問題,這顯然是不行的,會有很大的隐患。

是以Python開發者想了一個辦法就是設計了一個裝飾器,叫做abc。這裡的abc不是随意起的名字,其實是abstract basement class的縮寫。加上了這個裝飾器之後,它會替我們去檢查所有子類有沒有實作抽象方法。如果沒有的話,那麼會報錯提示我們。

我們用上six和abc這兩個包之後實作出來的基類是這樣的:

import abc
import six


@six.add_metaclass(abc.ABCMeta)
class Handler(object):

    def __init__(self, successor=None):
        self.successor = successor

    def handle(self, request):
        res = self.check_range(request)
        if not res and self.successor:
            self.successor.handle(request)

    # 抽象方法
    @abc.abstractmethod
    def check_range(self, request):
        pass
           

複制

實作類

我們把抽象類相關的概念以及責任鍊的原理了解了之後,就可以很簡單地寫出來實作類了。

所謂的實作類也就是抽象類的子類,實作了各自的抽象方法。這樣我們在每個節點當中調用self.successor.handle的時候才可以執行。因為每一個successor都是繼承了同樣一個抽象類的實作類,它們都必然實作了handle這個抽象方法。是以整個調用的過程就像是鍊條一樣串聯了起來。

在這個例子當中,我們用數字表示每個handler的處理範圍。隻有落在範圍裡的請求才會被響應,如果目前handler無法響應,那麼就會調用successor的handle繼續嘗試。

class ConcreteHandler(Handler):

    @staticmethod
    def check_range(request):
        if 0 <= request < 10:
            print('request {} handled in handler 0'.format(request))
            return True

    
class ConcreteHandler1(Handler):

    start, end = 10, 20

    def check_range(self, request):
        if self.start <= request < self.end:
            print('request {} handled in handler 1'.format(request))
            return True


class ConcreteHandler2(Handler):

    def check_range(self, request):
        start, end = self.get_interval()
        if start <= request < end:
            print('request {} handled in handler 2'.format(request))
            return True

    @staticmethod
    def get_interval():
        return (20, 30)


class FallBackHandler(Handler):

    @staticmethod
    def check_range(request):
        print('end of chain, no handler for {}'.format(request))
        return False
           

複制

這裡要注意一下FallBackHandler這個類,這個類表示責任鍊的結尾,也就是它是最後一個節點。如果請求一直穿過前面的節點送到了它這裡,就表示前面沒有相應的handler,這個請求無法響應。

最後我們來看下實際運作的例子:

if __name__ == '__main__':
    h0 = ConcreteHandler()
    h1 = ConcreteHandler1()
    h2 = ConcreteHandler2(FallBackHandler())
    h0.successor = h1
    h1.successor = h2

    requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
    for x in requests:
        h0.handle(x)
           

複制

我們可以看到我們建立了一系列handler的執行個體,然後将它們串聯了起來。如果我們運作這段代碼的話會發現我們不同的請求被不同的handler響應了。

原創 | 從抽象類開始,詳解責任鍊模式

到這裡關于責任鍊這個設計模式的介紹就結束了,這個設計模式非常巧妙,将上遊和下遊分裂開來,彼此之間消除了耦合。但是這也并不是完美的,它也有自己的缺點,一個很明顯的缺點就是這樣的效率會比較低。因為一層層鍊路傳遞都是需要時間的,尤其是這些鍊路如果還要遠端調用的話,那麼每一層轉換都會有延遲以及相應時間,是以整個系統的效率可能會不高。

還有呢就是我們調試的時候可能會不太友善,錯誤不太容易排查。除此之外還有一些其他的優缺點,大家可以自己思考一下,在下方留言。

衷心祝願大家每天都有所收獲。如果還喜歡今天的内容的話,請來一個三連支援吧~(點贊、在看、轉發)

- END -