iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
自我挑戰組

我推的Laravel系列 第 13

【Day-12】我推的Laravel-進階篇-OOP & SOLID

  • 分享至 

  • xImage
  •  

簡介

剛結束Coding Style,今天介紹軟體工程的觀念OOP和SOLID
這兩個觀念都是程式設計風格、原則或者說是典範
當然不限用於PHP或者Laravel

以下筆者個人認為

OOP和SOLID部分方面很像
它們的目的都是強調程式的重用性、可讀性、可測試性、可維護性、可擴展性,形成高內聚、低耦合

OOP

OOP,(Object-oriented programming,物件導向程式設計),是種具有物件概念的程式設計典範,同時也是一種程式開發的抽象方針。它可能包含資料、特性、程式碼與方法。物件則指的是類別(class)的實例。

  • 類(Class):定義了一件事物的抽象特點。類的定義包含了數據的形式以及對數據的操作。
  • 物件(Object):是類的實例(Instance)。
  • 介面(Interface):提供了便於代碼在概念上解釋的抽象層,並建立了避免依賴的一個屏障。

記得在學期間學到OOP一句話讓筆者印象深刻

在OOP的世界中,每一項事物都是一個物件,萬物皆物件

舉例來說,今天要定義一類動物,
此類動物具有髮色
並可以 睡覺
如果寫入PHP大概會是:

class Animal
{
    public $head;
    public $legs;
    public $hairColor;
    
    public function climb()
    {
        echo 'climb';
    }
    public function sleep()
    {
        echo 'sleep';
    }
    public function eat()
    {
        echo 'eat';
    }
}

其中public(公有)依需求可以寫作protected(保護)、private(私有),
這牽扯到繼承問題,之後會介紹,有興趣也可以自己深入~

而今天要實例化這個類別:

use Animal;
class PostController
{
    public function index()
    {
        $animal = new Animal();
        $animal->climb();
    }
}

被實例化後的類(Animal),可以被視為一物件($animal)

以上大致上就是OOP的簡易範例

SOLID

SOLID的觀念更加複雜

  • S 單一功能原則(Single responsibility principle):認為「對象應該僅具有一種單一功能」的概念。
  • O 開閉原則(The Open/Closed Principle, OCP):認為「軟體應該是對於擴充開放的,但是對於修改封閉的」的概念。
  • L 里氏替換原則(Liskov Substitution principle):認為「程式中的對象應該是可以在不改變程式正確性的前提下被它的子類所替換的」的概念。
  • I 介面隔離原則(Liskov Substitution principle):認為「多個特定客戶端介面要好於一個寬泛用途的介面」的概念。
  • D 依賴反轉原則(Dependency inversion principle):認為一個方法應該遵從「依賴於抽象而不是一個實例」的概念。依賴注入是該原則的一種實現方式。

以上可能有點抽象,筆者試著用Animal舉正例及反例,分別解釋五個原則的基本概念,如果有誤麻煩留言告知我orz

S 單一功能原則(Single responsibility principle)

正例: Animal僅有它具有的屬性及功能,如:頭、吃
反例: Animal新增一功能是付款,這並不符合動物的功能

O 開閉原則(The Open/Closed Principle, OCP)

符合 OCP 的示例:

使用繼承和多態性:假設您有一個 Animal 基類別,並且派生了具體的動物類別,如 Cat 和 Dog。每個具體的動物類別可以擴展其行為,而不需要修改 Animal 基類別。這符合 OCP,因為您可以輕鬆地添加新的動物類別而無需修改現有的程式碼。

class Animal {
    // 基本屬性和方法
}

class Cat extends Animal {
    public function makeSound() {
        echo 'Meow';
    }
}

class Dog extends Animal {
    public function makeSound() {
        echo 'Woof';
    }
}

使用介面:如果您定義一個介面(例如 AnimalInterface)來表示所有動物都應該具有的方法,然後您的具體動物類別實現這個介面,這也符合 OCP。您可以輕鬆地添加新的動物類別,只需確保它實現了介面中的方法。

interface AnimalInterface {
    public function makeSound();
}

class Cat implements AnimalInterface {
    public function makeSound() {
        echo 'Meow';
    }
}

class Dog implements AnimalInterface {
    public function makeSound() {
        echo 'Woof';
    }
}

違反 OCP 的示例:

直接修改基類別:如果您需要添加新的行為或修改現有的行為,但直接修改 Animal 基類別的程式碼,這就違反了 OCP。這會影響到現有的程式碼,可能會引入錯誤。

class Animal {
    public function makeSound() {
        echo 'Generic animal sound';
    }
}

// 違反 OCP:直接修改基類別

class Cat extends Animal {
    public function makeSound() {
        echo 'Meow';
    }
}

條件式邏輯:如果您在程式碼中使用大量的條件式邏輯,以根據不同的情況執行不同的行為,這也可能違反 OCP。當您需要添加新的情況時,您必須修改現有的程式碼,這不是對修改封閉的。

class Animal {
    public function makeSound($type) {
        if ($type === 'cat') {
            echo 'Meow';
        } elseif ($type === 'dog') {
            echo 'Woof';
        } else {
            echo 'Generic animal sound';
        }
    }
}

總之,OCP 讓你可以擴展功能而無需修改現有的程式碼,並且可以通過繼承、介面、多態性等方式實現。

L 里氏替換原則(Liskov Substitution principle)

長方形與正方形的例子
長方形:

class Rectangle{
  public width;
  public height;
  public function setWidth($w){
    $this.width = w;
  }
  public function setHeight($h){
    $this.height = h;
  } 
  public function getArea(){
      return $this->width * $this.height;
  }
}

正方形:

class Square extends Rectangle{
  public function setWidth($w){
    $this.width = w;
    $this.height = h;
  }
  public function setHeight($h){
    $this.width = w;
    $this.height = h;
  } 
  public function getArea(){
      return $this->width * $this.height;
  }
}
use Rectangle;
class PostController
{
    public function index()
    {
        $rectangle = new Rectangle();
        $rectangle->setWidth(20);
        $rectangle->setWidth(10);
        $rectangle->getArea(); // 200
    }
}

1.Square不僅違反OCP(因為它重寫setWidth、setHeight)
2.也違反里氏替換原則(如果把Rectangle替換成Square,得出來的結果會是100)

I 介面隔離原則(Interface-segregation principles)

介面隔離原則強調介面不應被迫使用對其而言無用的方法或功能,盡可能的將臃腫龐大的interface拆分成小而具體

以汽車為例:

interface Hybrid
{
    public $bigBattery;
    public $tank;
    public function fastCharge();
    public function burningFuel();
}
class HybridCar implements Hybrid
{
}

以上是不好的示範:
以下則有符合介面隔離原則

interface Electronic
{
    public $bigBattery;
    public function fastCharge();
}

interface Fuel
{
    public $tank;
    public function burningFuel();
}

class HybridCar implements Electronic, Fuel
{
}
class FuelCar implements Fuel
{
}

D 依賴反轉原則(Dependency inversion principle)

依賴反轉的理念在於分解高層的組件對低組件的依賴,這時候有個關鍵:介面(interface)
維基有提供一個例子是

檯燈與按鈕:

在此例子中,檯燈是低層組件、按鈕則是高層
檯燈中定義一介面(定義了開關),解開了檯燈與按鈕的耦合性

不符合依賴反轉原則:

use UserService;
class Post
{
    public function index(){
        $userService = new UserService();// 直接實例化低層次模組
        $user = $userService->find(1);
    }
}

符合:

use UserService;
class Post
{
    protected $userService;

    public function __construct(UserService $userService) {
        $this->userService = $userService;
    }

    public function index(){
        // 使用注入的 userService 依賴進行查詢
        $user = $this->userService->find(1)
    }
}

下一篇會利用Laravel的Service Container、Service Provider在深入依賴注入

總結

筆者在之前也不太清楚SOLID的詳細定義(現在也不清楚XD
這篇帶著讀者以最簡單的例子了解到SOLID的定義及重要性

不知道讀者是否和本人有同樣的疑問
分這麼細是否有效率問題
以下是個人見解:
前面提到這些設計風格是讓程式增加重用性、可讀性、可測試性、可維護性、可擴展性,形成高內聚、低耦合
就像你拿原生PHP和Laravel比效率,肯定是原生比較快
但是所帶來的開發時長、維護等等可能超乎預期
同時開發者在開發時為了快速而造成程式碼難以維護及重用,也是一項重大課題

參考

物件導向程式設計 - Wiki
深入淺出 Liskov 替換原則 Liskov Substitution Principle
SOLID (物件導向設計)


上一篇
【Day-11】我推的Laravel-進階篇-Coding Style
下一篇
【Day-13】我推的Laravel-進階篇-Service & Repository Pattern
系列文
我推的Laravel31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言