Really it comes down to the Single responsibility principle( http://en.wikipedia.org/wiki/Single_responsibility_principle )
You can view the Validator as a defensive machine mechanism that doesn't need to know the human comsetic side of the equation which is the generating of the human text . The actual requirement for the implementation of both may change seperately, once rules are written they have a habit of staying the same while the messages get changed a lot as it is the part the client can actually attach to and gain a sense of ownership. Plus attaching the Validator to the human text generation forces you to see implementation you do not have to see. When looking at lots of code it can become tiring having to contend with things outside the current remit to hunt down the bit you need to change.
The working pattern is also different.
Without the interface single Validator class it goes
rule->human text->rule->human text etc.
Whereas splitting the two up it goes
rule->rule etc // all rules are done in one coding phase
human text->human text // all text generations are done in one coding phase. Human text is completely different as it has it's own set of rules with grammar etc.
To tell you the truth Interfaces are quite hard to work out and often invlove a kind of reversal of brain. You isolate a task such as this validation and while creating it you decide it's calling requirements to passed objects and define their methods by an interface ensuring a looser coupling(http://en.wikipedia.org/wiki/Loose_coupling).
There is a solid clear design on the rules it will operate in( such as validate email format etc ) and no consideration has to be given to the final visual output. It really allows a complete detachment from everything else going on, which I personally find relaxing.
In this case using the interface method might be viewed as overkill by some, but there is a good case for it like passing all objects by interface. PHP ides are just a bit sucky when it comes to generating the methods after implementing the interface in a class.
It is a hard topic and one that can only really be learnt by using them and seeing their benefits over time. The benefit of interfaces is also that multiple can be implemented in one class and that each thing it is passed to only has knowledge of a designated subset. Strong coupling allows the potential of too much knowledge which depending on the people you work with can allow abuse by accessing things outside their remit.
We'll change the code a little bit and make ValidationResultListenerVisualTestThingy extend Command..
class EnquiryValidator{
/**
* @var Enquiry
*/
private $enquiry;
/**
* @var ValidationResultListenerVisualTestThingy
*/
private $listener;
/**
* Whether these are passed on initialisation etc is more a design choice
* @param Enquiry $enquiry
* @param ValidationResultListener $listener
*/
function __construct( Enquiry $enquiry, ValidationResultListenerVisualTestThingy $listener ){
$this->enquiry = $enquiry;
$this->listener = $listener;
$this->listener->GetDb(); // EnquiryValidator now has allowed access to GetDb, if it autocompletes in PHP it's classed as allowed as we have to rely on trust
}
public function Validate(){
$this->_ValidateFirstName();
$this->_ValidateLastName();
}
private function _ValidateFirstName(){
if( $this->enquiry->GetFirstName() == '' ){
$this->listener->RegisterEnquiryFirstNameEmptyError();
}
}
private function _ValidateLastName(){
if( $this->enquiry->GetLastName() == '' ){
$this->listener->RegisterEnquiryLastNameEmptyError();
}
}
}
class Command{
private $db;
function __construct(){
}
function GetDb(){
return $this->db;
}
}
class ValidationResultListenerVisualTestThingy extends Command implements EnquiryValidationStatusListener{
private $hasErrors = false;
private $errorList = array();
public function __construct(){
}
public function TestFirstNameError(){
$this->_TestEquiryWithValidator( '' , ' LastName' );
$this->_DumpResults( 'TestFirstNameError' );
}
public function TestLastNameError(){
$this->_TestEquiryWithValidator( 'FirstName' , ' ' );
$this->_DumpResults( 'TestLastNameError' );
}
public function TestBothEmpty(){
$this->_TestEquiryWithValidator( '' , ' ' );
$this->_DumpResults( 'TestBothEmpty' );
}
public function TestBothValid(){
$this->_TestEquiryWithValidator( 'FirstName' , 'LastName' );
$this->_DumpResults( 'TestBothValid' );
}
private function _TestEquiryWithValidator( $firstName, $lastName ){
$enquiry = new Enquiry();
$enquiry->SetFirstName( $firstName );
$enquiry->SetLastName ( $lastName );
$validator = new EnquiryValidator( $enquiry , $this );
$validator->Validate();
$this->_SetPostValidationErrorStatus();
}
private function _SetPostValidationErrorStatus(){
$this->hasErrors = count( $this->errorList ) > 0;
}
private function _DumpResults( $testMethodName ){
echo $testMethodName . ' produced these errors<br/>'
. implode( '<br/>' , $this->errorList )
. '<br>error status is currently ' . (int)$this->hasErrors . '<br/><br/>';
$this->_ResetErrrors();
}
private function _ResetErrrors(){
$this->hasErrors = false;
$this->errorList = array();
}
function RegisterEnquiryFirstNameEmptyError(){
$this->errorList[] = 'The first name is empty'; // could come from db in a multi language enviroment with no impact on the rules engine design
}
function RegisterEnquiryLastNameEmptyError(){
$this->errorList[] = 'The last name is empty';
}
}
If we were to pass the ValidationResultListenerVisualTestThingy to the EnquiryValidator ( ValidationResultListenerVisualTestThingy is used in the type hinting in EnquiryValidator constructor. ValidationResultListenerVisualTestThingy now extends command that has a public method to get the database connection. EnquiryValidator now has been given rights to access the database. It does not want access to the database or need it but because we have changed ValidationResultListenerVisualTestThingy it now has it. By increasing the functionality of ValidationResultListenerVisualTestThingy we have potentially increaed the functionality of the EnquiryValidator.
Now I like to look at this in two ways.
1. The happy way:
Classes have personalities and rights. They have the right to say I do not want to possibly know this, this level of access is all I need and this level of access is all you are going to give me or you can shove off. This is my interface requirements, you don't like it tough. I am in my own happy world
and if I do my job well I want to be left alone. I am not changing for you unless their is a damn good reason, I am not doing your job either.
- The paranoid unhappy I've seen many good ideas extremely creatively perverted into forms of work that can make life hell.:
Someone comes along and instead of making a thoughtful design decesion uses the GetDb inside the validator to do something freaky because their hands move faster than their brains and they want to get back to messanging their friends.
Anyway I need to get some sleep as it is gone midnight here. Hopefully I have made it a bit clearer.