Специфікація (англ. Specification) — це шаблон проєктування, за допомогою якого представлення правил бізнес
логіки може бути перетворено на ланцюжок об’єктів, пов’язаних операціями булевої логіки.
Цей шаблон виділяє такі специфікації (правила) у бізнес-логіці, які підходять для “зчеплення” з іншими специфікаціями.
Об’єкт бізнес-логіки успадковує свою функціональність від абстрактного класу, що агрегує SpecificationInterface
,
який містить лише один метод, SpecificationInterface->IsSatisfiedBy()
, що повертає булеве значення. Після інстанціювання
об’єкт об’єднується в ланцюжок з іншими об’єктами. В результаті, не втрачаючи гнучкості в налаштуванні, бізнес-логіки,
ми можемо легко додавати нові правила.
-
AndSpecification.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Class AndSpecification * @package DesignPatterns\Behavioral\Specification */ class AndSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * @param Item $item * * @return bool */ public function isSatisfiedBy(Item $item): bool { $satisfied = []; foreach ($this->specifications as $specification) { $satisfied[] = $specification->isSatisfiedBy($item); } return !in_array(false, $satisfied); } }
-
Item.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Class Item * @package DesignPatterns\Behavioral\Specification */ class Item { /** * @var float */ private $price; /** * Item constructor. * * @param float $price */ public function __construct(float $price) { $this->price = $price; } /** * @return float */ public function getPrice(): float { return $this->price; } }
-
NotSpecification.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Class NotSpecification * @package DesignPatterns\Behavioral\Specification */ class NotSpecification implements SpecificationInterface { /** * @var SpecificationInterface */ private $specification; /** * NotSpecification constructor. * * @param SpecificationInterface $specification */ public function __construct(SpecificationInterface $specification) { $this->specification = $specification; } /** * @param Item $item * * @return bool */ public function isSatisfiedBy(Item $item): bool { return !$this->specification->isSatisfiedBy($item); } }
-
OrSpecification.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Class OrSpecification * @package DesignPatterns\Behavioral\Specification */ class OrSpecification implements SpecificationInterface { /** * @var SpecificationInterface[] */ private $specifications; /** * @param SpecificationInterface[] ...$specifications */ public function __construct(SpecificationInterface ...$specifications) { $this->specifications = $specifications; } /** * @param Item $item * * @return bool */ public function isSatisfiedBy(Item $item): bool { $satisfied = []; foreach ($this->specifications as $specification) { $satisfied[] = $specification->isSatisfiedBy($item); } return in_array(true, $satisfied); } }
-
PriceSpecification.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Class PriceSpecification * @package DesignPatterns\Behavioral\Specification */ class PriceSpecification implements SpecificationInterface { /** * @var float */ private $maxPrice; /** * @var float */ private $minPrice; /** * @param float $minPrice * @param float $maxPrice */ public function __construct(float $minPrice, float $maxPrice) { $this->minPrice = $minPrice; $this->maxPrice = $maxPrice; } /** * @param Item $item * * @return bool */ public function isSatisfiedBy(Item $item): bool { if ($item->getPrice() > $this->maxPrice) { return false; } if ($item->getPrice() < $this->minPrice) { return false; } return true; } }
-
SpecificationInterface.php
<?php namespace DesignPatterns\Behavioral\Specification; /** * Interface SpecificationInterface * @package DesignPatterns\Behavioral\Specification */ interface SpecificationInterface { /** * @param Item $item * * @return bool */ public function isSatisfiedBy(Item $item): bool; }
Test case:
SpecificationTest.php
<?php
namespace DesignPatterns\Tests\Behavioral\Specification;
use DesignPatterns\Behavioral\Specification\Item;
use DesignPatterns\Behavioral\Specification\NotSpecification;
use DesignPatterns\Behavioral\Specification\OrSpecification;
use DesignPatterns\Behavioral\Specification\AndSpecification;
use DesignPatterns\Behavioral\Specification\PriceSpecification;
use PHPUnit_Framework_TestCase;
class SpecificationTest extends PHPUnit_Framework_TestCase
{
public function testCanOr()
{
$spec1 = new PriceSpecification(50, 99);
$spec2 = new PriceSpecification(101, 200);
$orSpec = new OrSpecification($spec1, $spec2);
$this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
$this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
$this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
}
public function testCanAnd()
{
$spec1 = new PriceSpecification(50, 100);
$spec2 = new PriceSpecification(80, 200);
$orSpec = new AndSpecification($spec1, $spec2);
$this->assertFalse($orSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($orSpec->isSatisfiedBy(new Item(1)));
$this->assertFalse($orSpec->isSatisfiedBy(new Item(51)));
$this->assertTrue($orSpec->isSatisfiedBy(new Item(100)));
}
public function testCanNot()
{
$spec1 = new PriceSpecification(50, 100);
$orSpec = new NotSpecification($spec1);
$this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($orSpec->isSatisfiedBy(new Item(50)));
}
}