設計模式 | Catalog設計模式,抵禦業務方需求變動

只愛宅zmy發表於2020-09-18
大家好,這是一個全新的專題—— 設計模式
其實可以選擇的專題還有好幾個,為什麼選擇設計模式呢?原因也很簡單,首先是設計模式 簡單、易學。乾貨的文章固然好,但是普適性往往不強。另外一個很重要的點就是設計模式學習的好處非常明顯,如果學得好的話,會覺得自己的 編碼能力有了質的突破。這並不是誇大其詞,很多人包括我,在學習的時候都曾經有過這種感覺。

設計模式簡介

設計模式這個詞我想大家應該都聽說過,但是它究竟是什麼意思可能很多人並不清楚。其實設計模式就是一種經驗,就是一種前人總結出來反覆印證過可以解決各種問題或者是做出各種最佳化的 程式碼設計經驗
以上是教科書當中的內容,下面是我個人的理解,在我看來設計模式主要有兩種用途,第一種是 最佳化我們的程式碼結構,讓我們的程式碼更加健壯,設計更加合理。我們讀大牛的程式碼常常驚歎,同樣的功能他怎麼這麼簡單就實現了,這個設計太巧妙了。設計模式就是這些令人驚歎的精彩設計的總結。第二種用途相對功利一些,是為了 抵抗業務邏輯變動。這一點如果你還沒有畢業的話,可能理解不深刻。在職場當中程式設計師最討厭什麼?其中很重要的一個點就是業務邏輯的變動,昨天才說了這裡要這麼設計,突然過了兩天就改了。或者是過了幾天突然增加了一個之前沒有想到的需求。而我們使用設計模式,一定程度上可以抵禦這樣的變更,儘量減少需求變動帶來程式碼的更改。
簡單總結一下,學習設計模式一方面可以讓我們的程式碼能力更強寫出來更優雅更牛的程式碼,另一方面可以幫助我們應對職場中需求,提升我們的表現和產出。
相比於這些好處最最重要的是,它的難度並不大,我們學習的成本不高。所以這是一件一本萬利的事情,說是程式設計師進階的必備技能也不為過。前面也說了設計模式是程式碼經驗的總結和提煉,所以它也和語言特性有關, 不同的語言實現出來的設計模式以及能夠實現的設計模式也不一樣。主流一般流行Java來實現設計模式,不過由於我們之前沒有介紹過Java相關的語法,我們這裡選擇使用Python的設計模式。程式碼參考借鑑了github中設計模式熱門repo: patterns,連結:
好了,廢話不多說,我們開始今天的內容。

目錄模式

今天要介紹的設計模式叫做Catalog,翻譯過來是目錄的意思。我沒有找到很好的中文資料,可能也許是因為Java當中不支援這種模式,而中文主流的設計模式都是Java為基礎的。
目錄設計模式的核心邏輯在於我們在一個類當中以方法的形式提供許多種功能,我們將這些功能以目錄的形式儲存在一個dict當中。我們在建立例項的時候 透過不同的引數獲取不同的功能,使用方在使用的時候不感知具體的引數。
這種設計模式有幾種實現方式,我們一個一個來看。

程式碼示例

基礎版本

class Catalog:
    def __init__(self, param):
        self._static_method_class = {'param_value_1': self._static_method_1, 'param_value_2': self._static_method_2}
        if param in self._static_method_class:
            self.param = param
        else:
            raise ValueError('Invalid value for param: {0}'.format(param))
    @staticmethod
    def _static_method_1(self):
        print('excuted method 1')
    @staticmethod
    def _static_method_2(self):
        print('excuted method 2')
    def main_method(self):
        self._static_method_class[self.param](self)
整個的邏輯很簡單,我們在init Catalog這個類的時候建立了一個_static_method_class dict,在這個dict當中我們的key是一個字串, value是Catalog這個類的兩個靜態方法。然後我們判斷param在不在dict當中,如果不在的話說明傳入的param有誤,我們丟擲一個異常。
在使用的時候呼叫的是main_method函式,在這個函式當中我們直接會根據self.param的當中的值執行對應的方法。這裡我們使用了靜態方法,靜態方法的好處是當我們建立它的子類的時候,靜態方法不會被子類覆蓋。當然這個靜態方法不是非常有必要,也可以去除靜態的邏輯,就 使用普通方法也是一樣可以執行的。

例項版本

上面的實現沒有問題,但是有一個地方有一點怪怪的,就是_static_method_class這個dict我們放在了例項當中。帶來的問題是如果這個dict很大,並且我們建立的例項很多的話,會 導致冗餘。因為既然所有例項的這個dict內容都是一樣的,那麼幹嘛存那麼多份呢,我們只需要存一份就可以了呀。
怎麼樣才能做到只存一份呢?也很簡單,我們只需要 把這個dict從例項域轉移到類域就可以了。也就是說把這個dict變成類當中的field,說白了也就是把它的定義挪到init方法外面。
class CatalogInstance:
    def __init__(self, param):
        self.x1 = 'x1'
        self.x2 = 'x2'
        if param in self._instance_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid value for param: {0}'.format(param))
    def _instance_method_1(self):
        print('Value {}'.format(self.x1))
    def _instance_method_2(self):
        print('Value {}'.format(self.x2))
    _instance_method_choices = {'param_value_1': _instance_method_1, 'param_value_2': _instance_method_2}
    def main_method(self):
        self._instance_method_choices[self.param](self)
這裡我們新增了一些細節,就是x1和x2這兩個引數。在這個例子當中,這兩個引數是寫死的,但是實際上我們 完全可以將它的初始化也寫在init方法當中。整體的邏輯和上面的版本大同小異,應該都可以看懂。
唯一一點需要注意的是,當我們在類的內部呼叫例項的方法的時候,都是透過self.xxxx來呼叫的,我們在呼叫的時候,直譯器會自動把當前例項作為第一個引數傳入其中,這也是為什麼例項級的方法前面第一個引數一定是self的原因。而這裡,我們是把例項方法存在了dict裡,透過dict取出來呼叫的。這種情況下, 直譯器並不會傳入self,所以我們自己需要加上self這個引數,否則的話就會引發報錯。

class和static版本

除了把_static_method_class這個dict放到了init方法的外面變成了類中的欄位之外,我們還可以對這個dict儲存的value做改動。我們可以把這兩個方法變成類級別的方法和靜態方法。雖然我個人覺得這樣改動的意義不是很大,但是也是一種方法,大家可以參考一下。
把方法變成類級別方法:
class CatalogClass:
    x1 = 'x1'
    x2 = 'x2'
    def __init__(self, param):
        if param in self._static_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid Value for param: {0}'.format(param))
    @classmethod
    def _static_method_1(cls):
        print('executed method 1')
    @classmethod
    def _static_method_2(cls):
        print('executed method 2')
    _static_method_choices = {'param_value_1': _static_method_1, 'param_value_2': _static_method_2}
    def main_method(self):
        self._static_method_choices[self.param].__get__(None, self.__class__)()
這裡有一點需要注意,就是我們把x1和x2這兩個引數也變成了類中的變數。因為 在classmethod當中我們是無法呼叫到例項域下的變數的,所以必須要將它們變成類級別當中的變數才可以訪問到。另外classmethod是規定了需要傳入class的相關引數的,並且是不可以直接呼叫的,所以我們要使用__get__方法獲取原始的函式。
把方法變成靜態方法:
class CatalogStatic:
    def __init__(self, param):
        if param in self._static_method_choices:
            self.param = param
        else:
            raise ValueError('Invalid Value for param: {0}'.format(param))
    @staticmethod
    def _static_method_1():
        print('executed method 1')
    @staticmethod
    def _static_method_2():
        print('executed method 2')
    _static_method_choices = {'param_value_1': _static_method_1, 'param_value_2': _static_method_2}
    def main_method(self):
        self._static_method_choices[self.param].__get__(None, self.__class__)()
用法和上面類似,只是將classmethod換成了staticmethod而已。

用法淺談

關於目錄這個設計模式的講解到這裡就結束了,下面我們討論一下它的使用方法,究竟在什麼場景下我們會用到這麼個設計模式呢?
其實很簡單,就是我們 例項建立和使用是分離的場景。比如我們是某功能的提供方,而使用方是另外的人。我們提供類的例項給對方使用,這樣做的好處是如果一旦需求發生變化,比如說之前開發的功能A要加一些改動,我們只需要自己改動Catalog類當中的邏輯就可以了,下游可以不需要做任何修改。再比如我們可以把建立例項的時候傳入的引數做成可配置的,這樣我們就可以透過修改配置來呼叫不同的邏輯。
關於這個設計模式還有一些改動的方案,比如我們可以把引數的傳遞放在呼叫方。呼叫方獲得的例項都是一樣的,呼叫的時候傳入不同的引數獲得不同的效果。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983372/viewspace-2722214/,如需轉載,請註明出處,否則將追究法律責任。

相關文章