full visitor pattern withh double dispatch

This commit is contained in:
Trismegiste
2013-08-17 19:20:23 -04:00
parent 330a6dbcc3
commit 8de0462c4c
7 changed files with 205 additions and 97 deletions

View File

@@ -0,0 +1,51 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\Visitor;
use DesignPatterns\Visitor;
/**
* VisitorTest tests the visitor pattern
*/
class VisitorTest extends \PHPUnit_Framework_TestCase
{
protected $visitor;
protected function setUp()
{
$this->visitor = new Visitor\RolePrintVisitor();
}
public function getRole()
{
return array(
array(new Visitor\User("Dominik"), 'Role: User Dominik'),
array(new Visitor\Group("Administrators"), 'Role: Group: Administrators')
);
}
/**
* @dataProvider getRole
*/
public function testVisitSomeRole(Visitor\Role $role, $expect)
{
$this->expectOutputString($expect);
$role->accept($this->visitor);
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Mock
*/
public function testUnknownObject()
{
$mock = $this->getMockForAbstractClass('DesignPatterns\Visitor\Role');
$mock->accept($this->visitor);
}
}

32
Visitor/Group.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace DesignPatterns\Visitor;
/**
* An example of a Visitee : Group
*/
class Group extends Role
{
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name)
{
$this->name = (string) $name;
}
/**
* @return string
*/
public function getName()
{
return "Group: " . $this->name;
}
}

38
Visitor/Role.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
namespace DesignPatterns\Visitor;
/**
* Visitor Pattern
*
* Purpose:
* The Visitor Pattern lets you outsource operations on objects to other objects. The main reason to do this is to keep
* a seperation of concerns. But classes have to define an contract to allow visitors (the "accept" method in the example below).
*
* The contract is an abstract class but you can have also a clean interface
*/
abstract class Role
{
/**
* This method handle a double dispatch based on the shortname of the Visitee
*
* Feel free to override it if your object must call another visiting behavior
*
* @param \DesignPatterns\Visitor\RoleVisitor $visitor
*/
public function accept(RoleVisitor $visitor)
{
// this trick to simulate double-dispatch based on type-hinting
$fqcn = get_called_class();
preg_match('#([^\\\\]+)$#', $fqcn, $extract);
$visitingMethod = 'visit' . $extract[1];
if (!method_exists($visitor, $visitingMethod)) {
throw new \InvalidArgumentException("The visitor you provide cannot visit a $fqcn object");
}
call_user_func(array($visitor, $visitingMethod), $this);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace DesignPatterns\Visitor;
/**
* Visitor Pattern
*
* An implementation of a Visitor
*/
class RolePrintVisitor implements RoleVisitor
{
public function visitGroup(Group $role)
{
echo "Role: " . $role->getName();
}
public function visitUser(User $role)
{
echo "Role: " . $role->getName();
}
}

26
Visitor/RoleVisitor.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace DesignPatterns\Visitor;
/**
* Visitor Pattern
*
* The contract for the visitor
*/
interface RoleVisitor
{
/**
* Visit a user object
*
* @param \DesignPatterns\Visitor\User $role
*/
public function visitUser(User $role);
/**
* Visit a Group objet
*
* @param \DesignPatterns\Visitor\Group $role
*/
public function visitGroup(Group $role);
}

35
Visitor/User.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace DesignPatterns\Visitor;
/**
* Visitor Pattern
*
* One example for a visitee. Each visitee has to extends Role
*/
class User extends Role
{
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name)
{
$this->name = (string) $name;
}
/**
* @return string
*/
public function getName()
{
return "User " . $this->name;
}
}

View File

@@ -1,97 +0,0 @@
<?php
namespace DesignPatterns;
/**
* Visitor Pattern
*
* Purpose:
* The Visitor Pattern lets you outsource operations on objects to other objects. The main reason to do this is to keep
* a seperation of concerns. But classes have to define an interface to allow visitors (the "accept" method in the example below).
*
*/
interface RoleVisitor {
public function visit(Role $role);
}
interface Role {
public function getName();
}
class RolePrintVisitor implements RoleVisitor {
/**
* @param Role $role
*/
public function visit(Role $role) {
echo "Role: " . $role->getName();
}
}
class User implements Role {
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name) {
$this->name = (string) $name;
}
/**
* @param RoleVisitor $visitor
* @return void
*/
public function accept(RoleVisitor $visitor) {
$visitor->visit($this);
}
/**
* @return string
*/
public function getName() {
return "User " . $this->name;
}
}
class Group implements Role {
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct($name) {
$this->name = (string) $name;
}
/**
* @param RoleVisitor $visitor
* @return void
*/
public function accept(RoleVisitor $visitor) {
$visitor->visit($this);
}
/**
* @return string
*/
public function getName() {
return "Group: " . $this->name;
}
}
$user = new User("Dominik");
$group = new Group("Administrators");
$printer = new RolePrintVisitor();
$printer->visit($user);
$printer->visit($group);