3.9. Спецификация

3.9.1. Предназначение

Изгражда ясна спецификация на бизнес правилата, където обектите могат да бъдат проверени. Композитният клас на спецификация (The composite specification class) има един метод, наречен isSatisfiedBy, който връща или true или false в зависимост от това дали даден обект удовлетворява спецификацията.

3.9.2. Примери

3.9.3. UML Диаграма

Alt Specification UML Diagram

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}