Специфікація (англ. 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 )));
}
}