规则模式

目的

规则模式,通过制定一份明确的业务规则来检查对象是否满足业务要求。提供规则的类有一个名为 isSatisfiedBy 的方法,该方法会检查指定对象是否满足规则,返回相应的 truefalse

使用场景

  • RulerZ - PHP 规则模式具体实现

UML 类图

技术规范模式类图

代码

Item.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

class Item
{
private float $price;

public function __construct(float $price)
{
$this->price = $price;
}

public function getPrice(): float
{
return $this->price;
}
}

Specification.php

1
2
3
4
5
6
7
8
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

interface Specification
{
public function isSatisfiedBy(Item $item): bool;
}

OrSpecification.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

class OrSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;

/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$specifications)
{
$this->specifications = $specifications;
}

/*
* if at least one specification is true, return true, else return false
*/
public function isSatisfiedBy(Item $item): bool
{
foreach ($this->specifications as $specification) {
if ($specification->isSatisfiedBy($item)) {
return true;
}
}

return false;
}
}

PriceSpecification.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

class PriceSpecification implements Specification
{
private ?float $maxPrice;
private ?float $minPrice;

public function __construct(?float $minPrice, ?float $maxPrice)
{
$this->minPrice = $minPrice;
$this->maxPrice = $maxPrice;
}

public function isSatisfiedBy(Item $item): bool
{
if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
return false;
}

if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
return false;
}

return true;
}
}

AndSpecification.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

class AndSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;

/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$specifications)
{
$this->specifications = $specifications;
}

/**
* if at least one specification is false, return false, else return true.
*/
public function isSatisfiedBy(Item $item): bool
{
foreach ($this->specifications as $specification) {
if (!$specification->isSatisfiedBy($item)) {
return false;
}
}

return true;
}
}

NotSpecification.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification;

class NotSpecification implements Specification
{
private Specification $specification;

public function __construct(Specification $specification)
{
$this->specification = $specification;
}

public function isSatisfiedBy(Item $item): bool
{
return !$this->specification->isSatisfiedBy($item);
}
}

测试

Tests/SpecificationTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\Specification\Tests;

use DesignPatterns\Extend\Specification\Item;
use DesignPatterns\Extend\Specification\NotSpecification;
use DesignPatterns\Extend\Specification\OrSpecification;
use DesignPatterns\Extend\Specification\AndSpecification;
use DesignPatterns\Extend\Specification\PriceSpecification;
use PHPUnit\Framework\TestCase;

class SpecificationTest extends 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);

$andSpec = new AndSpecification($spec1, $spec2);

$this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
$this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
$this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
}

public function testCanNot()
{
$spec1 = new PriceSpecification(50, 100);
$notSpec = new NotSpecification($spec1);

$this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
}
}