规格模式(Specification)

目的

构建一个清晰的业务规则规范,其中每条规则都能被针对性地检查。每个规范类中都有一个称为isSatisfiedBy的方法,方法判断给定的规则是否满足规范从而返回 true 或 false。

例子

UML Diagram

file

代码

你可以在 GitHub 中找到这段代码。

Item.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. class Item
  4. {
  5. /**
  6. * @var float
  7. */
  8. private $price;
  9. public function __construct(float $price)
  10. {
  11. $this->price = $price;
  12. }
  13. public function getPrice(): float
  14. {
  15. return $this->price;
  16. }
  17. }

SpecificationInterface.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. interface SpecificationInterface
  4. {
  5. public function isSatisfiedBy(Item $item): bool;
  6. }

OrSpecification.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. class OrSpecification implements SpecificationInterface
  4. {
  5. /**
  6. * @var SpecificationInterface[]
  7. */
  8. private $specifications;
  9. /**
  10. * @param SpecificationInterface[] ...$specifications
  11. */
  12. public function __construct(SpecificationInterface ...$specifications)
  13. {
  14. $this->specifications = $specifications;
  15. }
  16. /**
  17. * 如果有一条规则符合条件,返回 true,否则返回 false
  18. */
  19. public function isSatisfiedBy(Item $item): bool
  20. {
  21. foreach ($this->specifications as $specification) {
  22. if ($specification->isSatisfiedBy($item)) {
  23. return true;
  24. }
  25. }
  26. return false;
  27. }
  28. }

PriceSpecification.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. class PriceSpecification implements SpecificationInterface
  4. {
  5. /**
  6. * @var float|null
  7. */
  8. private $maxPrice;
  9. /**
  10. * @var float|null
  11. */
  12. private $minPrice;
  13. /**
  14. * @param float $minPrice
  15. * @param float $maxPrice
  16. */
  17. public function __construct($minPrice, $maxPrice)
  18. {
  19. $this->minPrice = $minPrice;
  20. $this->maxPrice = $maxPrice;
  21. }
  22. public function isSatisfiedBy(Item $item): bool
  23. {
  24. if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
  25. return false;
  26. }
  27. if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
  28. return false;
  29. }
  30. return true;
  31. }
  32. }

AndSpecification.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. class AndSpecification implements SpecificationInterface
  4. {
  5. /**
  6. * @var SpecificationInterface[]
  7. */
  8. private $specifications;
  9. /**
  10. * @param SpecificationInterface[] ...$specifications
  11. */
  12. public function __construct(SpecificationInterface ...$specifications)
  13. {
  14. $this->specifications = $specifications;
  15. }
  16. /**
  17. * 如果有一条规则不符合条件,返回 false,否则返回 true
  18. */
  19. public function isSatisfiedBy(Item $item): bool
  20. {
  21. foreach ($this->specifications as $specification) {
  22. if (!$specification->isSatisfiedBy($item)) {
  23. return false;
  24. }
  25. }
  26. return true;
  27. }
  28. }

NotSpecification.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification;
  3. class NotSpecification implements SpecificationInterface
  4. {
  5. /**
  6. * @var SpecificationInterface
  7. */
  8. private $specification;
  9. public function __construct(SpecificationInterface $specification)
  10. {
  11. $this->specification = $specification;
  12. }
  13. public function isSatisfiedBy(Item $item): bool
  14. {
  15. return !$this->specification->isSatisfiedBy($item);
  16. }
  17. }

测试

Tests/SpecificationTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Specification\Tests;
  3. use DesignPatterns\Behavioral\Specification\Item;
  4. use DesignPatterns\Behavioral\Specification\NotSpecification;
  5. use DesignPatterns\Behavioral\Specification\OrSpecification;
  6. use DesignPatterns\Behavioral\Specification\AndSpecification;
  7. use DesignPatterns\Behavioral\Specification\PriceSpecification;
  8. use PHPUnit\Framework\TestCase;
  9. class SpecificationTest extends TestCase
  10. {
  11. public function testCanOr()
  12. {
  13. $spec1 = new PriceSpecification(50, 99);
  14. $spec2 = new PriceSpecification(101, 200);
  15. $orSpec = new OrSpecification($spec1, $spec2);
  16. $this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
  17. $this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
  18. $this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
  19. }
  20. public function testCanAnd()
  21. {
  22. $spec1 = new PriceSpecification(50, 100);
  23. $spec2 = new PriceSpecification(80, 200);
  24. $andSpec = new AndSpecification($spec1, $spec2);
  25. $this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
  26. $this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
  27. $this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
  28. $this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
  29. }
  30. public function testCanNot()
  31. {
  32. $spec1 = new PriceSpecification(50, 100);
  33. $notSpec = new NotSpecification($spec1);
  34. $this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
  35. $this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
  36. }
  37. }