<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何在 Python 中應(yīng)用設(shè)計(jì)原則

          共 8088字,需瀏覽 17分鐘

           ·

          2021-07-05 16:24

          寫出能用的代碼很簡單,寫出好用的代碼很難。

          什么是好用的代碼呢?其實(shí)就是代碼質(zhì)量比較高,如何評價代碼質(zhì)量的高低呢?最常用的、最重要的評價標(biāo)準(zhǔn),就是代碼的可維護(hù)性、可讀性、可擴(kuò)展性、靈活性、簡潔性、可復(fù)用性、可測試性。

          好用的代碼,也都會遵循一此原則,這就是設(shè)計(jì)原則,它們分別是:

          • 單一職責(zé)原則 (SRP)
          • 開閉原則 (OCP)
          • 里氏替換原則 (LSP)
          • 接口隔離原則 (ISP)
          • 依賴倒置原則 (DIP)

          提取這五種原則的首字母縮寫詞,就是 SOLID 原則。下面分別進(jìn)行介紹,并展示如何在 Python 中應(yīng)用。

          1、單一職責(zé)原則 SRP

          單一職責(zé)原則(Single Responsibility Principle)這個原則的英文描述是這樣的:A class or module should have a single responsibility。如果我們把它翻譯成中文,那就是:一個類或者模塊只負(fù)責(zé)完成一個職責(zé)(或者功能)。

          讓我們舉一個更簡單的例子,我們有一個數(shù)字 L = [n1, n2, …, nx] 的列表,我們計(jì)算一些數(shù)學(xué)函數(shù)。例如,計(jì)算最大值、平均值等。

          一個不好的方法是讓一個函數(shù)來完成所有的工作:

          import numpy as np

          def math_operations(list_):
              # Compute Average
              print(f"the mean is {np.mean(list_)}")
              # Compute Max
              print(f"the max is {np.max(list_)}"

          math_operations(list_ = [1,2,3,4,5])
          # the mean is 3.0
          # the max is 5

          實(shí)際開發(fā)中,你可以認(rèn)為 math_operations 很龐大,揉雜了各種功能代碼。

          為了使這個更符合單一職責(zé)原則,我們應(yīng)該做的第一件事是將函數(shù) math_operations 拆分為更細(xì)粒度的函數(shù),一個函數(shù)只干一件事:

          def get_mean(list_):
              '''Compute Max'''
              print(f"the mean is {np.mean(list_)}"

          def get_max(list_):
              '''Compute Max'''
              print(f"the max is {np.max(list_)}"

          def main(list_): 
              # Compute Average
              get_mean(list_)
              # Compute Max
              get_max(list_)

          main([1,2,3,4,5])
          # the mean is 3.0
          # the max is 5

          這樣做的好處就是:

          • 易讀易調(diào)試,更容易定位錯誤。
          • 可復(fù)用,代碼的任何部分都可以在代碼的其他部分中重用。
          • 可測試,為代碼的每個功能創(chuàng)建測試更容易。

          但是要增加新功能,比如計(jì)算中位數(shù),main 函數(shù)還是很難維護(hù),因此還需要第二個原則:OCP。

          2、開閉原則 OCP

          開閉原則(Open Closed Principle)就是對擴(kuò)展開放,對修改關(guān)閉,這可以大大提升代碼的可維護(hù)性,也就是說要增加新功能時只需要添加新的代碼,不修改原有的代碼,這樣做即簡單,也不會影響之前的單元測試,不容易出錯,即使出錯也只需要檢查新添加的代碼。

          上述代碼,可以通過將我們編寫的所有函數(shù)變成一個類的子類來解決這個問題。代碼如下:

          import numpy as np
          from abc import ABC, abstractmethod

          class Operations(ABC):
              '''Operations'''
              @abstractmethod
              def operation():
                  pass

          class Mean(Operations):
              '''Compute Max'''
              def operation(list_):
                  print(f"The mean is {np.mean(list_)}"

          class Max(Operations):
              '''Compute Max'''
              def operation(list_):
                  print(f"The max is {np.max(list_)}"

          class Main:
              '''Main'''
              def get_operations(list_):
                  # __subclasses__ will found all classes inheriting from Operations
                  for operation in Operations.__subclasses__():
                      operation.operation(list_)


          if __name__ == "__main__":
              Main.get_operations([1,2,3,4,5])
          # The mean is 3.0
          # The max is 5

          如果現(xiàn)在我們想添加一個新的操作,例如:median,我們只需要添加一個繼承自 Operations 類的 Median 類。新形成的子類將立即被 __subclasses__()接收,無需對代碼的任何其他部分進(jìn)行修改。

          3、里氏替換原則 (LSP)

          里式替換原則的英文是 Liskov Substitution Principle,縮寫為 LSP。這個原則最早是在 1986 年由 Barbara Liskov 提出,他是這么描述這條原則的:

          If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。

          也就是說 子類對象能夠替換程序中父類對象出現(xiàn)的任何地方,并且保證原來程序的邏輯行為不變及正確性不被破壞。

          實(shí)際上,里式替換原則還有另外一個更加能落地、更有指導(dǎo)意義的描述,那就是按照協(xié)議來設(shè)計(jì),子類在設(shè)計(jì)的時候,要遵守父類的行為約定(或者叫協(xié)議)。父類定義了函數(shù)的行為約定,那子類可以改變函數(shù)的內(nèi)部實(shí)現(xiàn)邏輯,但不能改變函數(shù)原有的行為約定。這里的行為約定包括:函數(shù)聲明要實(shí)現(xiàn)的功能;對輸入、輸出、異常的約定;甚至包括注釋中所羅列的任何特殊說明。

          4、接口隔離原則 (ISP)

          接口隔離原則的英文翻譯是 Interface Segregation Principle,縮寫為 ISP。Robert Martin 在 SOLID 原則中是這樣定義它的:Clients should not be forced to depend upon interfaces that they do not use。

          直譯成中文的話就是:客戶端不應(yīng)該被強(qiáng)迫依賴它不需要的接口。其中的 客戶端 ,可以理解為接口的調(diào)用者或者使用者。

          舉個例子:

          from abc import ABC, abstractmethod
          class Mammals(ABC):

              @abstractmethod
              def swim(self) -> bool:
                  pass

              @abstractmethod
              def walk(self) -> bool:
                  pass


          class Human(Mammals):
              def swim(self)-> bool:
                  print("Humans can swim")
                  return True

              def walk(self)-> bool:
                  print("Humans can walk")
                  return True


          class Whale(Mammals):

              def walk(self) -> bool:
                  print("Whales can't walk")
                  return False

              def swim(self):
                  print("Whales can swim")
                  return True


          human = Human()
          human.swim()
          human.walk()
          whale = Whale()
          whale.swim()
          whale.walk()

          執(zhí)行結(jié)果:

          Humans can swim
          Humans can walk
          Whales can swim
          Whales can't walk

          事實(shí)上,子類鯨魚不應(yīng)該依賴它不需要的接口 walk,針對這種情況,就需要對接口進(jìn)行拆分,代碼如下:

          from abc import ABC, abstractmethod

          class Swimer(ABC):

              @abstractmethod
              def swim(self) -> bool:
                  pass

          class Walker(ABC):
              @abstractmethod
              def walk(self) -> bool:
                  pass


          class Human(Swimer,Walker):
              def swim(self)-> bool:
                  print("Humans can swim")
                  return True

              def walk(self)-> bool:
                  print("Humans can walk")
                  return True


          class Whale(Swimer):

              def swim(self):
                  print("Whales can swim")
                  return True


          human = Human()
          human.swim()
          human.walk()
          whale = Whale()
          whale.swim()

          5、依賴反轉(zhuǎn)原則 (DIP)

          依賴反轉(zhuǎn)原則的英文翻譯是 Dependency Inversion Principle,縮寫為 DIP。英文描述:High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions。

          我們將它翻譯成中文,大概意思就是:高層模塊不要依賴低層模塊。高層模塊和低層模塊應(yīng)該通過抽象(abstractions)來互相依賴。除此之外,抽象不要依賴具體實(shí)現(xiàn)細(xì)節(jié),具體實(shí)現(xiàn)細(xì)節(jié)依賴抽象。

          在調(diào)用鏈上,調(diào)用者屬于高層,被調(diào)用者屬于低層,我們寫的代碼都屬于低層,由框架來調(diào)用。在平時的業(yè)務(wù)代碼開發(fā)中,高層模塊依賴低層模塊是沒有任何問題的,但是在框架層面設(shè)計(jì)的時候,就要考慮通用性,高層應(yīng)該依賴抽象的接口,低層應(yīng)該實(shí)現(xiàn)對應(yīng)的接口。如下圖所示:

          也就是說本來 ObjectA 依賴 ObjectB,但為了擴(kuò)展后面可能會有 ObjectC,ObjectD,經(jīng)常變化,因此為了頻繁改動,讓高層模塊依賴抽象的接口 interface,然后讓 ObjectB 也反過來依賴 interface,這就是依賴反轉(zhuǎn)原則。

          舉個例子,wsgi 協(xié)議就是一種抽象接口,高層模塊有 uWSGI,gunicorn等,低層模塊有 Django,F(xiàn)lask 等,uWSGI,gunicorn 并不直接依賴 Django,F(xiàn)lask,而是通過 wsgi 協(xié)議進(jìn)行互相依賴。

          依賴倒置原則概念是高層次模塊不依賴于低層次模塊。看似在要求高層次模塊,實(shí)際上是在規(guī)范低層次模塊的設(shè)計(jì)。低層次模塊提供的接口要足夠的抽象、通用,在設(shè)計(jì)時需要考慮高層次模塊的使用種類和場景。明明是高層次模塊要使用低層次模塊,對低層次模塊有依賴性。現(xiàn)在反而低層次模塊需要根據(jù)高層次模塊來設(shè)計(jì),出現(xiàn)了「倒置」的顯現(xiàn)。

          這樣設(shè)計(jì)好處有兩點(diǎn):

          1. 低層次模塊更加通用,適用性更廣
          2. 高層次模塊沒有依賴低層次模塊的具體實(shí)現(xiàn),方便低層次模塊的替換

          最后的話

          我去年(2020)年 2 月 3 號購買的《設(shè)計(jì)模式之美》專欄,將近一年半才把它學(xué)習(xí)完,再回看之前的代碼,真是一堆垃圾。之前一天寫完的代碼,重構(gòu)差不多花了一個星期,重構(gòu)之后,感覺還可以再重構(gòu)的更好,似乎無止境,正如小爭哥說的那樣,項(xiàng)目無論大小,都可以有技術(shù)含量,所謂代碼細(xì)節(jié)是魔鬼,細(xì)節(jié)到處都存在在取舍,應(yīng)該怎么樣,不應(yīng)該怎么樣,都大有學(xué)問。

          推薦閱讀

          解釋器模式實(shí)戰(zhàn):實(shí)現(xiàn)自定義的告警規(guī)則功能

          暗號:回復(fù)關(guān)鍵詞 「100」,獲取高質(zhì)量手把手 Python 教程,讓你 100 天成為 Python 高手。

          留言


          瀏覽 37
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无码艹逼 | 成年人免费黄色视频网站 | 亚洲天堂色 | 日本在线视频二区 | 国产激情在线内射 |