3.9. Спецификация (Specification)
3.9.1. Назначение
Строит ясное описание бизнес-правил, на соответствие которым могут быть проверены объекты. Композитный класс спецификация имеет один метод, называемый isSatisfiedBy
, который возвращает истину или ложь в зависимости от того, удовлетворяет ли данный объект спецификации.
3.9.2. Примеры
3.9.3. Диаграмма UML
3.9.4. Код
Вы можете найти этот код на GitHub
Item.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7class Item
8{
9 public function __construct(private float $price)
10 {
11 }
12
13 public function getPrice(): float
14 {
15 return $this->price;
16 }
17}
Specification.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7interface Specification
8{
9 public function isSatisfiedBy(Item $item): bool;
10}
OrSpecification.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7class OrSpecification implements Specification
8{
9 /**
10 * @var Specification[]
11 */
12 private array $specifications;
13
14 /**
15 * @param Specification[] $specifications
16 */
17 public function __construct(Specification ...$specifications)
18 {
19 $this->specifications = $specifications;
20 }
21
22 /*
23 * if at least one specification is true, return true, else return false
24 */
25 public function isSatisfiedBy(Item $item): bool
26 {
27 foreach ($this->specifications as $specification) {
28 if ($specification->isSatisfiedBy($item)) {
29 return true;
30 }
31 }
32
33 return false;
34 }
35}
PriceSpecification.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7class PriceSpecification implements Specification
8{
9 public function __construct(private ?float $minPrice, private ?float $maxPrice)
10 {
11 }
12
13 public function isSatisfiedBy(Item $item): bool
14 {
15 if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
16 return false;
17 }
18
19 if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
20 return false;
21 }
22
23 return true;
24 }
25}
AndSpecification.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7class AndSpecification implements Specification
8{
9 /**
10 * @var Specification[]
11 */
12 private array $specifications;
13
14 /**
15 * @param Specification[] $specifications
16 */
17 public function __construct(Specification ...$specifications)
18 {
19 $this->specifications = $specifications;
20 }
21
22 /**
23 * if at least one specification is false, return false, else return true.
24 */
25 public function isSatisfiedBy(Item $item): bool
26 {
27 foreach ($this->specifications as $specification) {
28 if (!$specification->isSatisfiedBy($item)) {
29 return false;
30 }
31 }
32
33 return true;
34 }
35}
NotSpecification.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification;
6
7class NotSpecification implements Specification
8{
9 public function __construct(private Specification $specification)
10 {
11 }
12
13 public function isSatisfiedBy(Item $item): bool
14 {
15 return !$this->specification->isSatisfiedBy($item);
16 }
17}
3.9.5. Тест
Tests/SpecificationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Specification\Tests;
6
7use DesignPatterns\Behavioral\Specification\Item;
8use DesignPatterns\Behavioral\Specification\NotSpecification;
9use DesignPatterns\Behavioral\Specification\OrSpecification;
10use DesignPatterns\Behavioral\Specification\AndSpecification;
11use DesignPatterns\Behavioral\Specification\PriceSpecification;
12use PHPUnit\Framework\TestCase;
13
14class SpecificationTest extends TestCase
15{
16 public function testCanOr()
17 {
18 $spec1 = new PriceSpecification(50, 99);
19 $spec2 = new PriceSpecification(101, 200);
20
21 $orSpec = new OrSpecification($spec1, $spec2);
22
23 $this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
24 $this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
25 $this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
26 }
27
28 public function testCanAnd()
29 {
30 $spec1 = new PriceSpecification(50, 100);
31 $spec2 = new PriceSpecification(80, 200);
32
33 $andSpec = new AndSpecification($spec1, $spec2);
34
35 $this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
36 $this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
37 $this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
38 $this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
39 }
40
41 public function testCanNot()
42 {
43 $spec1 = new PriceSpecification(50, 100);
44 $notSpec = new NotSpecification($spec1);
45
46 $this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
47 $this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
48 }
49}