diff --git a/README.md b/README.md index 3727c5d..2a5a75c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The patterns can be structured in roughly three different categories. Please cli * [Mediator](Mediator) [:notebook:](http://en.wikipedia.org/wiki/Mediator_pattern) * [NullObject](NullObject) [:notebook:](http://en.wikipedia.org/wiki/Null_Object_pattern) * [Observer](Observer) [:notebook:](http://en.wikipedia.org/wiki/Observer_pattern) +* [Specification](Specification) [:notebook:](http://en.wikipedia.org/wiki/Specification_pattern) * [State](State) [:notebook:](http://en.wikipedia.org/wiki/State_pattern) * [Strategy](Strategy) [:notebook:](http://en.wikipedia.org/wiki/Strategy_pattern) * [TemplateMethod](TemplateMethod) [:notebook:](http://en.wikipedia.org/wiki/Template_method_pattern) diff --git a/Specification/AbstractSpecification.php b/Specification/AbstractSpecification.php new file mode 100755 index 0000000..e7fcc8e --- /dev/null +++ b/Specification/AbstractSpecification.php @@ -0,0 +1,53 @@ +left = $left; + $this->right = $right; + } + + /** + * Returns the evaluation of both wrapped specifications as a logical AND + * + * @param Item $item + * + * @return bool + */ + public function isSatisfiedBy(Item $item) + { + return $this->left->isSatisfiedBy($item) || $this->right->isSatisfiedBy($item); + } +} \ No newline at end of file diff --git a/Specification/Item.php b/Specification/Item.php new file mode 100644 index 0000000..0d46575 --- /dev/null +++ b/Specification/Item.php @@ -0,0 +1,31 @@ +price = $price; + } + + /** + * Get the items price + * + * @return int + */ + public function getPrice() + { + return $this->price; + } +} diff --git a/Specification/Not.php b/Specification/Not.php new file mode 100755 index 0000000..3e79cad --- /dev/null +++ b/Specification/Not.php @@ -0,0 +1,34 @@ +spec = $spec; + } + + /** + * Returns the negated result of the wrapped specification + * + * @param Item $item + * + * @return bool + */ + public function isSatisfiedBy(Item $item) + { + return !$this->spec->isSatisfiedBy($item); + } +} \ No newline at end of file diff --git a/Specification/Plus.php b/Specification/Plus.php new file mode 100755 index 0000000..32c3801 --- /dev/null +++ b/Specification/Plus.php @@ -0,0 +1,37 @@ +left = $left; + $this->right = $right; + } + + /** + * Checks if the composite AND of specifications passes + * + * @param Item $item + * + * @return bool + */ + public function isSatisfiedBy(Item $item) + { + return $this->left->isSatisfiedBy($item) && $this->right->isSatisfiedBy($item); + } +} \ No newline at end of file diff --git a/Specification/PriceSpecification.php b/Specification/PriceSpecification.php new file mode 100644 index 0000000..fd8387f --- /dev/null +++ b/Specification/PriceSpecification.php @@ -0,0 +1,50 @@ +maxPrice = $maxPrice; + } + + /** + * Sets the optional minimum price + * + * @param int $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * Checks if Item price falls between bounds + * + * @param Item $item + * + * @return bool + */ + public function isSatisfiedBy(Item $item) + { + if ( !empty($this->maxPrice) && $item->getPrice() > $this->maxPrice) { + return false; + } + if ( !empty($this->minPrice) && $item->getPrice() < $this->minPrice) { + return false; + } + + return true; + } +} diff --git a/Specification/SpecificationInterface.php b/Specification/SpecificationInterface.php new file mode 100755 index 0000000..b8c1e7c --- /dev/null +++ b/Specification/SpecificationInterface.php @@ -0,0 +1,38 @@ +assertTrue($spec->isSatisfiedBy($item)); + + $spec->setMaxPrice(50); + $this->assertFalse($spec->isSatisfiedBy($item)); + + $spec->setMaxPrice(150); + $this->assertTrue($spec->isSatisfiedBy($item)); + + $spec->setMinPrice(101); + $this->assertFalse($spec->isSatisfiedBy($item)); + + $spec->setMinPrice(100); + $this->assertTrue($spec->isSatisfiedBy($item)); + } + + public function testNotSpecification() + { + $item = new Item(100); + $spec = new PriceSpecification(); + $not = $spec->not(); + + $this->assertFalse($not->isSatisfiedBy($item)); + + $spec->setMaxPrice(50); + $this->assertTrue($not->isSatisfiedBy($item)); + + $spec->setMaxPrice(150); + $this->assertFalse($not->isSatisfiedBy($item)); + + $spec->setMinPrice(101); + $this->assertTrue($not->isSatisfiedBy($item)); + + $spec->setMinPrice(100); + $this->assertFalse($not->isSatisfiedBy($item)); + } + + public function testPlusSpecification() + { + $spec1 = new PriceSpecification(); + $spec2 = new PriceSpecification(); + $plus = $spec1->plus($spec2); + + $item = new Item(100); + + $this->assertTrue($plus->isSatisfiedBy($item)); + + $spec1->setMaxPrice(150); + $spec2->setMinPrice(50); + $this->assertTrue($plus->isSatisfiedBy($item)); + + $spec1->setMaxPrice(150); + $spec2->setMinPrice(101); + $this->assertFalse($plus->isSatisfiedBy($item)); + + $spec1->setMaxPrice(99); + $spec2->setMinPrice(50); + $this->assertFalse($plus->isSatisfiedBy($item)); + } + + public function testEitherSpecification() + { + $spec1 = new PriceSpecification(); + $spec2 = new PriceSpecification(); + $either = $spec1->either($spec2); + + $item = new Item(100); + + $this->assertTrue($either->isSatisfiedBy($item)); + + $spec1->setMaxPrice(150); + $spec2->setMaxPrice(150); + $this->assertTrue($either->isSatisfiedBy($item)); + + $spec1->setMaxPrice(150); + $spec2->setMaxPrice(0); + $this->assertTrue($either->isSatisfiedBy($item)); + + $spec1->setMaxPrice(0); + $spec2->setMaxPrice(150); + $this->assertTrue($either->isSatisfiedBy($item)); + + $spec1->setMaxPrice(99); + $spec2->setMaxPrice(99); + $this->assertFalse($either->isSatisfiedBy($item)); + } +}