什麼是規格模式#
核心概念#
規格模式核心概念
規格模式將業務規則封裝成可重用和可組合的物件。它將物件的核心屬性和業務邏輯分離,使複雜的條件判斷更加清晰和易於維護。
使用時機#
適用情境
- 當業務規則複雜且經常變化時
- 需要將不同的業務規則組合使用時
- 要避免在一個類別中擁有太多複雜的判斷邏輯時
- 需要讓業務規則可以重用和測試時
UML#

實作#
建立基本實體類別(Basic Entity)#
實體類別設計
首當其中我們需要把大頭菜物件給建立出來,具有價格(price)以及數量(count)的記錄、讀取功能。在規格模式中,實體類別只負責核心屬性,而複雜的業務邏輯則由規格類別負責。
Turnips.php#
/**
* Class Turnips.
*/
class Turnips
{
/**
* @var int
*/
protected int $price = 0;
/**
* @var int
*/
protected int $count = 0;
/**
* Turnips constructor.
*
* @param int $price
* @param int $count
*/
public function __construct(int $price, int $count)
{
$this->price = $price;
$this->count = $count;
}
/**
* @return int
*/
public function getPrice(): int
{
return $this->price;
}
/**
* @return int
*/
public function getCount(): int
{
return $this->count;
}
}
定義規格介面(Specification Interface)#
規格介面設計
再來定義規格模組(Specification)的介面,這個介面定義了所有具體規格類別必須實作的方法。它將實作大頭菜尚未補足的邏輯,也就是計算鈴錢價格(calculatePrice)的這項功能。
Specification.php#
/**
* Interface Specification.
*/
interface Specification
{
/**
* @return int
*/
public function calculatePrice(): int;
}
實作具體規格(Concrete Specifications)#
正常大頭菜規格
最後我們有兩種規格模式,分別是正常的大頭菜、壞掉的大頭菜。我們先來實作正常的情況下,大頭菜的總計鈴錢計算規格。這裡提供了可以處理大頭菜集合的方式,展現了規格模式的彈性。
TurnipsSpecification.php#
/**
* Class TurnipsSpecification.
*/
class TurnipsSpecification implements Specification
{
/**
* @var Turnips[]
*/
protected array $turnips;
/**
* Turnips constructor.
*
* @param Turnips[] $turnips
*/
public function __construct(Turnips ...$turnips)
{
$this->turnips = $turnips;
}
/**
* @return int
*/
public function calculatePrice(): int
{
$total = 0;
foreach ($this->turnips as $turnip) {
$total += $turnip->getPrice() * $turnip->getCount();
}
return $total;
}
}
壞掉大頭菜規格
最後是實作壞掉的大頭菜計算模式,這裡也是一樣提供一顆或多顆大頭菜計算。不一樣的點在於因為是壞掉的大頭菜,無法賣出鈴錢,所以無論你丟過來幾顆,都會回傳 0 鈴錢。這展現了不同規格可以包裝其他規格的組合性特徵。
SpoiledSpecification.php#
/**
* Class SpoiledSpecification.
*/
class SpoiledSpecification implements Specification
{
/**
* @var Specification[]
*/
protected array $turnips;
/**
* SpoiledSpecification constructor.
*
* @param Specification[] $turnips
*/
public function __construct(Specification ...$turnips)
{
$this->turnips = $turnips;
}
/**
* @return int
*/
public function calculatePrice(): int
{
return 0;
}
}
測試#
測試目標#
測試清單
最後我們要寫個測試,來測試規格模式是不是正確的,驗證規格的封裝和組合能力:
單顆大頭菜測試: 單顆大頭菜使用正常規格模組,是否能正常計算出鈴錢價格
多顆大頭菜測試: 多顆大頭菜使用正常規格模組,是否能正常計算出鈴錢價格
單顆組合規格測試: 單顆大頭菜使用正常規格模組,再套用壞掉的規格,是否能正常計算出壞掉的鈴錢價格
多顆組合規格測試: 多顆大頭菜使用正常規格模組,再套用壞掉的規格,是否能正常計算出壞掉的鈴錢價格
測試程式碼#
SpecificationPatternTest.php#
/**
* Class SpecificationPatternTest.
*/
class SpecificationPatternTest extends TestCase
{
/**
* @test
*/
public function test_single_turnips()
{
$turnips = new Turnips(100, 40);
$specification = new TurnipsSpecification($turnips);
$this->assertEquals(4000, $specification->calculatePrice());
}
/**
* @test
*/
public function test_multi_turnips()
{
$turnips_A = new Turnips(100, 40);
$turnips_B = new Turnips(90, 20);
$turnips_C = new Turnips(110, 20);
$specification = new TurnipsSpecification($turnips_A, $turnips_B, $turnips_C);
$this->assertEquals(8000, $specification->calculatePrice());
}
/**
* @test
*/
public function test_single_spoiled()
{
$turnips = new Turnips(100, 40);
$specification = new TurnipsSpecification($turnips);
$spoiled = new SpoiledSpecification($specification);
$this->assertEquals(0, $spoiled->calculatePrice());
}
/**
* @test
*/
public function test_multi_spoiled()
{
$turnips_A = new Turnips(100, 40);
$turnips_B = new Turnips(90, 20);
$turnips_C = new Turnips(110, 20);
$specification = new TurnipsSpecification($turnips_A, $turnips_B, $turnips_C);
$spoiled = new SpoiledSpecification($specification);
$this->assertEquals(0, $spoiled->calculatePrice());
}
}
測試結果#
最後測試的執行結果會獲得如下:
PHPUnit Pretty Result Printer 0.28.0 by Codedungeon and contributors.
==> Configuration: ~/php-design-pattern/vendor/codedungeon/phpunit-result-printer/src/phpunit-printer.yml
PHPUnit 9.2.6 by Sebastian Bergmann and contributors.
==> ...fResponsibilitiesTest ✔ ✔ ✔
==> CommandPatternTest ✔
==> IteratorPatternTest ✔ ✔ ✔ ✔
==> MediatorPatternTest ✔ ✔ ✔
==> MementoPatternTest ✔
==> NullObjectPatternTest ✔ ✔ ✔ ✔
==> ObserverPatternTest ✔
==> SpecificationPatternTest ✔ ✔ ✔ ✔
==> AbstractFactoryTest ✔ ✔ ✔ ✔
==> BuilderPatternTest ✔ ✔ ✔ ✔
==> FactoryMethodTest ✔ ✔ ✔ ✔
==> PoolPatternTest ✔ ✔
==> PrototypePatternTest ✔ ✔
==> SimpleFactoryTest ✔ ✔ ✔ ✔
==> SingletonPatternTest ✔
==> StaticFactoryTest ✔ ✔ ✔ ✔ ✔
==> AdapterPatternTest ✔ ✔
==> BridgePatternTest ✔ ✔ ✔
==> CompositePatternTest ✔ ✔ ✔
==> DataMapperTest ✔ ✔
==> DecoratorPatternTest ✔ ✔
==> DependencyInjectionTest ✔ ✔ ✔
==> FacadePatternTest ✔
==> FluentInterfaceTest ✔
==> FlyweightPatternTest ✔
==> ProxyPatternTest ✔ ✔
==> RegistryPatternTest ✔ ✔ ✔ ✔ ✔
Time: 00:00.036, Memory: 8.00 MB
OK (72 tests, 141 assertions)