想學會SOLID原則,看這一篇文章就夠了!

来源:https://www.cnblogs.com/aaron-948/archive/2022/05/06/16235001.html
-Advertisement-
Play Games

背景 在我們日常工作中,代碼寫著寫著就出現下列的一些臭味。但是還好我們有SOLID這把‘尺子’, 可以拿著它不斷去衡量我們寫的代碼,除去代碼臭味。這就是我們要學習SOLID原則的原因所在。 設計的臭味 僵化性 具有聯動性,動一處,會牽連到其他地方 脆弱性 不敢改動,動一處,全局癱瘓 頑固性 不易改動 ...


背景

在我們日常工作中,代碼寫著寫著就出現下列的一些臭味。但是還好我們有SOLID這把‘尺子’, 可以拿著它不斷去衡量我們寫的代碼,除去代碼臭味。這就是我們要學習SOLID原則的原因所在。

設計的臭味

  • 僵化性
    • 具有聯動性,動一處,會牽連到其他地方
  • 脆弱性
    • 不敢改動,動一處,全局癱瘓
  • 頑固性
    • 不易改動
  • 粘滯性
    • 耦合性太高
  • 不必要的複雜性
    • 代碼設計過於複雜
  • 不必要的重覆
    • 提高復用性,減少重覆
  • 晦澀性
    • 代碼設計不易理解

SRP-單一職責原則

  • 一個類只做一件事情。當然一件事情,不是說類中只有一個方法。而是類中的方法都是屬於同一種職責。
  • 不能因為第二職責的原因去改動這個類。

一個很好的例子:在我們封裝request庫時,我們需要實現以下4個方法.

class MyRequestClient:
    
    def post(self):
        pass
    def get(self):
        pass  
    def update(self):
        pass
    def delete(self):
        pass
        
    #上面的方法就是屬於同一職責。 如何還有其他的方法,那麼這個類就不符合單一職責原則。
    #例增加以下方法:
    def get_db_data(self):
        pass
    def to_object(self):
        pass
       

OCP-開放封閉原則

  • 對擴展開放,對修改封閉。
  • 無需改動自身代碼,就可以擴展它的行為。
  • 對類的改動往往是新增代碼就可以了,而不是去修改原有的代碼。
  • 使用子類繼承、依賴註入、數據驅動的方法可以實現OCP原則。

首先我們來看一個違反OCP原則的例子。

#bad code
def circle_draw():
    print(f"this is circle draw")

def square_draw():
    print(f"this is square draw")

def draw_all_shape(shapes):
    for shape in shapes:
        if shape == "circle":
            circle_draw()
        if shape == "square":
            square_draw()

這段代碼的問題是如果再有新的類型需要draw, 我們需要修改draw_all_shape函數來適配新的類型。

依賴註入實現OCP原則

我們定義了一個抽象類Shape, 子類Square和Circle繼承Shape. 並且在子類中重寫了父類的方法。函數draw_all_shape是繪製所有圖形。

from typing import List
from abc import ABCMeta, abstractmethod


class Shape(metaclass=ABCMeta):

    @abstractmethod
    def draw(self):
        pass


class Square(Shape):

    def draw(self):
        print(f"this is square draw")


class Circle(Shape):

    def draw(self):
        print(f"this is circle draw")


def draw_all_shape(shapes: List[Shape]):
    for shape in shapes:
        shape.draw()

我們定義了一個抽象類Shape, 子類SquareCircle繼承Shape. 並且在子類中重寫了父類的方法。函數draw_all_shape是繪製所有圖形。

參數註入實現OCP原則

def circle_draw():
    print(f"this is circle draw")


def square_draw():
    print(f"this is square draw")


def draw_all_shape_by_function(data: Dict[str,Callable]):
    for key,value in data.items():
        value()


data = {
    "circle": circle_draw,
    "square": square_draw
}

draw_all_shape_by_function(data=data)

Conclusion

  • 這樣的設計的好處是,如果需要再繪製一個三角形,那麼我們只需要增加一個新類並繼承Shape.無需修改shape類和draw_all_shape就可以實現三角形類的繪製。
  • 當我們在類中或函數中需要使用大量的if-else邏輯判斷時,很有可能代碼就違反了OCP原則。

LSP:Liskov 替換原則

  • 派生類應該可以替換父類中的方法使用,而不會改變程式原本的功能。
  • 派生類重寫方法的參數應該和父類的保持一致或多於父類,不能少於父類。
  • 派生類重寫方法的返回值必須和父類返回值類型一致。
  • 違反LSP原則,通常也會違反OCP原則。

首先我們來看一段違法LSP的例子

from typing import Iterable
class User():
    def __init__(self, user: str) -> None:
        self.user = user
    def disable(self) -> None:
        print(f"{self.user} disable!")        
  
  
class Admin(User):
    def __init__(self, user: str = "Admin") -> None:
        self.user = user
    def disable(self):
        raise "Admin do not disable!"
   
   
def delete_user(users: Iterable[User]):
    for user in users:
        user.disable()

當執行delete_user時,就會拋出TypeError 錯誤,Admin類中disable方法違法了LSP替換原則。

Optimize

#Good
from typing import Iterable

class User():
    def __init__(self, user: str) -> None:
        self.user = user

    def allow_disable(self):
        return True

    def disable(self) -> None:
        print(f"{self.user} disable!")        
    

class Admin(User):
    def __init__(self, user: str = "Admin") -> None:
        self.user = user

    def allow_disable(self):
        return False
    

def delete_user(users: Iterable[User]) -> None:
    for user in users:
        if user.allow_disable:
            user.disable()

Conclusion

  • 上例中通過添加allow_disable 的方法,解決了Admin類不能disable的問題。
  • 當派生類不正確的重寫父類方法的時候,就會違反LSP原則,我們在繼承類的時候重寫方法的時候,尤其- 要註意是否違反了LSP原則。

ISP 介面隔離原則

  • 客戶應該不依賴它不使用的方法。
  • 一個類只做一件事。

首先來看一個違反ISP原則的例子:

class Animal(metaclass=ABCMeta):

    @abstractclassmethod
    def run(self):
        pass

    @abstractclassmethod
    def speak(self):
        pass

    @abstractclassmethod
    def fly(self):
        pass


class Dog(Animal):

    def run(self):
        return "Dog Running"

    def speak(self):
        return "Dog Speaking"

    def fly(self):
        raise TypeError("Dog can not fly")


class Bird(Animal):

    def run(self):
        raise TypeError("Bird can not run")

    def speak(self):
        return "Bird Speaking"

    def fly(self):
        return "Bird fly"


def fly_animal(animals: Iterable[Animal]):
    for animal in animals:
        animal.fly()

當我們執行fly_animal時,就會拋出TypeError的錯誤。此時Animal抽象類是一個胖類,違法了ISP原則。

Optimize

  • 將Animal抽象類分解為三個新抽象類,FlyingAnimal, TalkingAnimal, RunningAnimal, 底層代碼按需繼承。
#good
class FlyingAnimal(metaclass=ABCMeta):

    @abstractclassmethod
    def fly(self):
        pass


class RunningAnimal(metaclass=ABCMeta):

    @abstractclassmethod
    def run(self):
        pass


class TalkingAnimal(metaclass=ABCMeta):

    @abstractclassmethod
    def talk(self):
        pass


class Dog(RunningAnimal,TalkingAnimal):

    def run(self):
        return "Dog Running"

    def talk(self):
        return "Dog Speaking"


class Bird(FlyingAnimal, TalkingAnimal):

    def talk(self):
        return "Bird Speaking"

    def fly(self):
        return "Bird fly"


def fly_animal(animals: Iterable[FlyingAnimal]):
    for animal in animals:
        print(animal.fly())

Conclusion

  • 介面隔離原則看似和單一職責原則相似,單一職責原則是針對模塊,類,方法的設計。介面隔離原則更註重在調用者的角度,按需提供介面。
  • 寫更小的類,大多數情況下是個好主意。
  • 違反ISP原則也可能會違反LSP原則和SRP原則。
  • 當子類重寫了一個不需要的方法時,很可能違反了ISP原則。

DIP 依賴倒置原則

  • 程式中所有的依賴都應該終止於抽象類或介面。
  • 任何類都不應該從具體類派生。
  • 任何方法都不易應該重寫它的任何基類已經實現了的方法。
  • 高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象。

首先看一個違反DIP原則的例子:

class Lamp:
    def turn_on(self):
        print("turn on the lamp")
    
    def turn_off(self):
        print("turn off the lamp")


class Button():

    def __init__(self) -> None:
        self.lamp = Lamp()
    
    def turn_on(self):
        return self.lamp.turn_on()

    def turn_off(self):
        return self.lamp.turn_off()

當有一天,button需要控制televsion時,就需要修改Button類。ButtonLamp 具有強耦合關係。所以,當Lamp變動時,會影響到Button類。違法了DIP原則的高層模塊依賴於底層模塊。

Optimize

定義一個抽象類ElectricAppliance Button 和 Lamp 都依賴這個抽象類。 解決了ButtonLamp 具有強耦合的問題。

class ElectricAppliance(metaclass=ABCMeta):

    @abstractmethod
    def turn_on(self):
        pass

    @abstractmethod
    def turn_off(self):
        pass


class Lamp(ElectricAppliance):
    def turn_on(self):
        print("turn on the lamp")

    def turn_off(self):
        print("turn off the lamp")


class Television(ElectricAppliance):
    def turn_on(self):
        print("turn on the televison")

    def turn_off(self):
        print("turn off the televison")


class Button:

    def __init__(self, electric_appliance: ElectricAppliance) -> None:
        self.electric_appliance = electric_appliance

    def turn_on(self):
        self.electric_appliance.turn_on()

    def turn_off(self):
        self.electric_appliance.turn_off()

Conclusion

  • 要確定代碼是否違反了DIP原則,需要觀察一個類中是否嵌入了調用其他類或函數。如果是,那麼很可能是違反了DIP原則。

本文來自博客園,作者:煙熏柿子學編程,轉載請註明原文鏈接:https://www.cnblogs.com/aaron-948/p/16235001.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 變聲是直播類、聊天類應用中用戶經常使用的功能。例如:很多主播選擇使用變聲器來實現帶動直播間氣氛;和朋友語音聊天時選擇變成蘿莉音讓聊天更有趣。HMS Core音頻編輯服務提供變聲能力,幫助開發者在應用中構建變聲功能。用戶可以通過預置的變聲風格進行變聲,提升音頻可玩性的同時有效保護用戶隱私,讓你隨心所欲 ...
  • 技術大咖們從開源實戰項目總結經驗,利用真實場景的應用案例分享前沿技術,引導開發者從零參與 OpenHarmony 開源貢獻,提升代碼效率,培養開發者成為開源社區的貢獻者。 ...
  • 1. 準備階段 關於該功能的實現我們需要學習以下的資料: 1.1 【ARKUI】ets怎麼實現文件操作 1.2 文件管理 1.3 Ability上下文 2. demo 實現 2.1 文件路徑讀取 參考 context.getFilesDir 來進行獲取文件路徑,代碼如下 private getCac ...
  • 今天做了一個案例,可以好好做做能夠將之前的內容結合起來,最主要的是能對組件化編碼流程有一個大概的清晰認知,這一套做下來,明天自己再做一遍複習一下,其實組件化流程倒是基本上沒什麼問題了,主要是很多vue的方法需要多熟悉一下,畢竟打破了之前的一些對於傳統js的認知,還需要多熟悉一下。 這兩天可能內容不是 ...
  • 大家好,我是半夏👴,一個剛剛開始寫文的沙雕程式員.如果喜歡我的文章,可以關註➕ 點贊 👍 加我微信:frontendpicker,一起學習交流前端,成為更優秀的工程師~關註公眾號:搞前端的半夏,瞭解更多前端知識! 點我探索新世界! 原文鏈接 ==>http://sylblog.xin/archi ...
  • 一、主要區別 1、{} 和 new Object() 除了本身創建的對象,都繼承了 Object 原型鏈上(Object.prototype)的屬性或者方法,eg:toString();當創建的對象相同時,可以說 {} 等價於 new Object() 。2、Object.create() 是將創建 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 非同步編程的實現方式? JavaScript中的非同步機制可以分為以下幾種: 回調函數 的方式,使用回調函數的方式有一個缺點是,多個回調函數嵌套的時候會造成回調函數地獄,上下兩層的回調函數間的代碼耦合度太高,不利於代碼的可維護。 Pro ...
  • DOM 事件是處理 Web 頁面交互的基礎,是掌握前端開發技術的基礎。 W3C協會早在1988年就開始了DOM標準的制定,W3C DOM標準可以分為DOM1,DOM2,DOM3三個版本。 1.Html事件處理 原始事件模型,事件處理程式被設置為html控制項的性質值,一般是html控制項的 onclic ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...