PHP Abstract

消除 ifs 利用 Abstract 實踐多型

如果你的程式碼太多的 ifs or switch 的描述,或是四散在各地很多相同的ifs or switch 程式碼。常常改了一個,忘記改另外一個。其實可以利用 OO Abstract的方式,去實現多型polymorphsim,也可讓程式碼變得簡潔,容易維護,也容易測試。

這篇筆記主要是來自 Miško Hevery AngularJS之父,在Google Clean Code Talk的筆記。

讓我們看如何消除 ifs Hell吧!
https://gyazo.com/873ef982d75fb04159cc8afcec23b912

何時使用多型

  • Object的行為會因為狀態 State而不同。
  • 需要檢查相同的狀態在很多的地方。

Case1 案例一

https://gyazo.com/5d4f54dde4a01020a54fa130ee6bdaa6

讓我們創建第一版的Node來表示這樣的算式:

https://gyazo.com/6087e57859ac2e3b10aa8b29171f47c0

<?php
namespace DemoAbstract;

class Node
{
    public $operator; // string
    public $value;
    public $leftNode;
    public $rightNode;
    public function evaluate():float
    {
        switch ($this->operator) {
            case '#':
                return $this->value;
            case '+':
                return $this->leftNode->evaluate() + $this->rightNode->evaluate();
            case '*':
                return $this->leftNode->evaluate() * $this->rightNode->evaluate();
            //case ....
        }
    }
}

當運算多了一種時,就需要在switch case增加。
觀察 1 + 2 * 3,Node的物件就變成了下圖:
https://gyazo.com/1198ee03f66f6738757ebc83c8576656

代碼的如下:

$OneNode = new Node();
$OneNode->value = 1.0;
$OneNode->operator = '#';

$TwoNode = new Node();
$TwoNode->value = 2.0;
$TwoNode->operator = '#';

$ThreeNode = new Node();
$ThreeNode->value = 3.0;
$ThreeNode->operator = '#';

$MultiNode = new Node();
$MultiNode->operator = '*';
$MultiNode->leftNode = $TwoNode;
$MultiNode->rightNode = $ThreeNode;
echo $MultiNode->evaluate();

第一版的改進

更進一步分析,其實我們並沒有用到許多Node的屬性:

https://gyazo.com/612442b8f8e2b16c097e441432ce3105

我們應該可以把Node做些抽離,分成 value node和 operation node。
https://gyazo.com/f00f37856fdfddaeef9cafd2d4a605d2

ValueNode變得很Lightweight。

abstract class BaseNode
{
    abstract public function evaluate():float;
}

class ValueNode extends BaseNode
{
    public $value;
    public function evaluate():float
    {
        return $this->value;
    }
}

下面為 OpNode,可以把數字的情況給移除:

class OpNode extends BaseNode
{
    public $operator;
    public $leftNode;
    public $rightNode;
    public function evaluate():float
    {
        switch ($this->operator) {
            case '+':
                return $this->leftNode->evaluate() + $this->rightNode->evaluate();
            case '*':
                return $this->leftNode->evaluate() * $this->rightNode->evaluate();
            //case ....
        }
    }
}

如果用這樣的方法,我們生成的物件實例如下圖:
https://gyazo.com/1a432870bd7df21d862c94d06571e24e

執行的代碼如下:

$OneValueNode = new ValueNode();
$TwoValueNode = new ValueNode();
$ThreeValueNode = new ValueNode();

$OneValueNode->value = 1.0;
$TwoValueNode->value = 2.0;
$ThreeValueNode->value = 3.0;

$MultiNode = new OpNode();
$MultiNode->operator = '*';
$MultiNode->leftNode = $TwoValueNode;
$MultiNode->rightNode = $ThreeValueNode;
echo $MultiNode->evaluate();

第二版的改進

開始針對 OpNode 做些分析:發覺只有Operator的不同:
https://gyazo.com/4cc3d34c544ee75cfe73d8f8319a71c2

因此我們應該可以在抽離OpNode如下圖:
https://gyazo.com/8f4e4d6545aa112c2c4100db150cd3da
因此我們的代碼會變成如下:

abstract class OpNode extends BaseNode
{
    public $leftNode;
    public $rightNode;
}

class AdditionNode extends OpNode
{
    public function evaluate():float
    {
        return $this->leftNode->evaluate() + $this->rightNode->evaluate();
    }
}

class MultiplicationNode extends OpNode
{
    public function evaluate():float
    {
        return $this->leftNode->evaluate() * $this->rightNode->evaluate();
    }
}

注意:OpNode變成為abstract class 繼承另一個 abstract class (BaseNode)。PHP程式碼允許這樣的擴充。

另外程式碼變得很簡潔,而且如果要新增加另一個運算時,只要再增加一個型別的OpNode,就可達到擴充性,而且不需要改到其他的運算Node。

相關的程式碼可以從這裡下載

結論

  • 利用多型當新的行為增加時,我們不需要更動到既有的程式碼。
  • 每個運算都在不同的檔案或Class,方便我們進行測試和理解。

results matching ""

    No results matching ""