3.9. Specification

3.9.1. Scopo

Costruire una specifica chiara delle logiche di business, che gli oggetti chiamati devono rispettare. La classe che implementa la specifica ha un metodo chiamato isSatisfiedBy che restituisce vero o falso se il dato oggetto rispetta la specifica.

3.9.2. Esempi

3.9.3. Diagramma UML

Alt Specification UML Diagram

3.9.4. Codice

Potete trovare questo codice anche su 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. Test

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}