iT邦幫忙

2023 iThome 鐵人賽

DAY 14
1
Software Development

從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!系列 第 14

Day 14 - 理解 OOP 物件導向程式設計的基本原則 - SOLID

  • 分享至 

  • xImage
  •  

繼上篇的物件導向程式設計概念,近期在面試上有遇到一個很重要的相關觀念,
當時不知道為什麼,現在整理出來分享~

:有聽過 SOLID 嗎?
:SOLID 是什麼?

SOLID 是什麼?

SOLID 是一組五個物件導向程式設計的基本原則,旨在幫助開發者創建可維護、可擴展且易於理解的程式碼。這些原則有助於確保軟體設計具有高內聚性和低耦合性,這對於長期維護和協作開發非常重要。

  1. 單一職責原則 (Single Responsibility Principle - SRP)

    • 一個模組應該只對唯一的一個角色負責。
    • 通過將不同的職責分開,我們可以輕鬆地修改和擴展每個類別,而不會影響其他程式碼。

    假設我們有一個名為 Employee 的類別,
    表示公司的員工,我們將根據 SRP 創建兩個具有不同職責的類別。

    1. EmployeeInfo 類別(負責員工的基本資訊):
    class EmployeeInfo
      attr_reader :name, :employee_id
    
      def initialize(name, employee_id)
        @name = name
        @employee_id = employee_id
      end
    
      def display_info
        "Name: #{@name}, Employee ID: #{@employee_id}"
      end
    end
    
    1. EmployeeSalary 類別(負責員工的薪水計算):
    class EmployeeSalary
      attr_reader :employee, :salary
    
      def initialize(employee, salary)
        @employee = employee
        @salary = salary
      end
    
      def calculate_bonus
        @salary * 0.1 # 假設獎金是薪水的10%
      end
    end
    

    以上兩個不同的類別,每個類別都具有不同的職責。

    • EmployeeInfo 負責管理員工的基本資訊(如姓名和員工ID)
    • EmployeeSalary 負責計算員工的薪水和獎金。
      這兩個類別各自專注於其單一職責,符合 SRP 的原則。

    attr_reader 是 Ruby 中一個用於自動生成讀取實例變數(instance variables)的方法的簡便方式。主要用途是使類別的實例變數能夠被外部程式碼讀取,而不需要額外編寫自訂的 getter 方法。attr_reader 通常用於創建只讀取(read-only)的實例變數,這意味著外部程式碼可以訪問變數的值,但不能修改它。
    以下是 attr_reader 的基本用法:

    class MyClass
      attr_reader :my_variable
    
      def initialize(value)
        @my_variable = value
      end
    end
    

    在這個示例中,attr_reader :my_variable 被用來定義一個名為 my_variable 的實例變數,並自動生成了一個名為 my_variable 的讀取方法(getter 方法)。這意味著我們可以在外部程式碼中訪問 my_variable 的值,如下所示:

    obj = MyClass.new(42)
    puts obj.my_variable # 輸出: 42
    

    需要注意的是,attr_reader 只創建了讀取方法,不允許外部程式碼修改實例變數的值。如果你需要允許變數被修改,則可以使用 attr_writerattr_accessor,分別用於生成寫入方法(setter 方法)或同時生成讀取和寫入方法。


  1. 開放-封閉原則 (Open-Closed Principle - OCP)
    • 這個原則強調軟體實體(如類別、模組、函數等)應該是開放擴展的,但封閉修改的。
    • 可以擴展現有的功能,而不必修改現有的程式碼。通常,這可以通過使用抽象化和多型來實現。
      可以往前篇回顧
      抽象化和多態性範例:Day 13 - 理解 Ruby - 物件導向程式設計語言

  1. 里氏替換原則 (Liskov Substitution Principle - LSP)

    • 這個原則強調子類別應該能夠替換其基類別,而不會破壞程式的正確性。換句話說,如果某個程式使用基類別,則應該可以安全地替換為其子類別,而不會導致不正確的行為。

    假設我們有一個基類別 Bird,代表鳥類,並且有一個 fly 方法用來表示鳥類飛行的行為:

    class Bird
      def fly
        "This bird can fly."
      end
    end
    

    然後,我們建立了一個子類別 Penguin,代表企鵝,但企鵝不能飛行,所以我們需要重寫 fly 方法:

    class Penguin < Bird
      def fly
        "This penguin can't fly."
      end
    end
    

    現在,我們可以使用里氏替換原則,確保子類別 Penguin 可以替換其基類別 Bird,並且在不影響程式正確性的情況下重寫 fly 方法。

    bird = Bird.new
    penguin = Penguin.new
    
    puts bird.fly  # 輸出: "This bird can fly."
    puts penguin.fly  # 輸出: "This penguin can't fly."
    

    雖然 Penguin 重寫了 fly 方法,但它仍然可以替換 Bird 並遵守里氏替換原則。這使得我們能夠根據需要擴展程式碼,同時保持對基類別的一致性使用,而不會破壞程式的正確性。


  1. 介面隔離原則 (Interface Segregation Principle - ISP)

    • 這個原則建議將一個大型的、具有多個方法的介面分解成多個小型的、專注於特定用途的介面。這樣可以避免實現不需要的方法,從而減少類別之間的耦合性,並提高代碼的可讀性。

    假設我們正在建立一個文件系統應用程式,需要處理文件(File)和資料夾(Folder)。我們想確保這些元素都可以執行基本的操作,如開啟(open)、關閉(close)和刪除(delete)。

    我們可以創建兩個模組,分別是 OpenableDeletable

    module Openable
      def open
        puts "Opening..."
      end
    end
    
    module Deletable
      def delete
        puts "Deleting..."
      end
    end
    

    接下來,我們創建文件(File)和資料夾(Folder)類別,並混入相應的模組:

    class File
      include Openable
      include Deletable
    
      def initialize(name)
        @name = name
      end
    
      def name
        @name
      end
    end
    
    class Folder
      include Deletable
    
      def initialize(name)
        @name = name
        @contents = []
      end
    
      def add_item(item)
        @contents << item
      end
    end
    

    在以上例子中,FileFolder 類別分別混入了 OpenableDeletable 模組。這樣,File 可以執行開啟、關閉和刪除操作,而 Folder 只能執行刪除操作。 確保每個類別僅實現了他們需要的方法,而不包含不必要的方法,從而減少不必要的耦合。 如果未來需要添加新的操作,可以輕鬆擴展相應的模組,而不會影響其他部分的程式碼。

  2. 依賴反轉原則 (Dependency Inversion Principle - DIP)

    • 這個原則強調高層次模組不應該依賴於低層次模組,兩者都應該依賴於抽象。此外,抽象不應該依賴於具體實現,而具體實現應該依賴於抽象。這可以通過使用介面或抽象類別來實現。

    假設我們有一個介面 Device 代表設備,並且我們希望能夠將不同類型的設備連接到應用程式。
    設備可以執行 connect 操作。

    # 設備介面
    class Device
      def connect
      end
    end
    
    # 具體設備類別
    class Keyboard < Device
      def connect
        puts "Keyboard connected."
      end
    end
    
    class Mouse < Device
      def connect
        puts "Mouse connected."
      end
    end
    

    我們定義了一個 Device 類別,並建立兩個具體的設備類別 KeyboardMouse,分別覆蓋了 connect 方法以提供實際的連接功能。

    這樣,當我們需要添加新的設備類別時,只需創建一個新的設備類別,覆蓋 connect 方法,透過這方式實現 DIP,因為高層模組(使用這些設備的程式碼)依賴於 Device 抽象介面,而不需要關心具體的設備類別。


SOLID 原則通常與設計模式和良好的軟體設計實踐一起使用,以實現高品質的軟體。
當設計和編寫程式碼時,考慮這些原則可以幫助避免常見的設計問題並提高代碼的質量。
但是,即便這樣簡短的講完這些內容,還是必須反覆去複習才會理解!

今天先分享至此,我們下篇待續~!

參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 13 - 理解 Ruby - 物件導向程式設計語言
下一篇
Day 15 - 理解 Ruby on Rails 是什麼?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言