Hi...

Originally Posted by
westbroek
And as they have to be aware of eachother - the problem is: how?
Class names are intrinsicly global. This means that any class can create an instance of another at will. However this is not usually a good idea unless such classes are very closely related. Here are all the options that I can think of...
1) Pass it in as needed in the method. This is the approach with the least coupling and so, if you can, you want to do this as a first choice...
PHP Code:
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint(new XhtmlWriter());
In this case I would imagine that the "&" would be automatically escaped before it was printed to the screen.
2) Pass it in the constructor so that it is available when needed...
PHP Code:
$template = new Template(new XhtmlWriter());
$template->substitute('title', 'You & me are here');
$template->paint();
This is handy if you are going to call more than just the paint() method and the writer will be needed in each case. Duplication is bad and this avoids it. This is called the Strategy pattern and the tool of choice in OO.
3) Create the object internally...
PHP Code:
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$writer = new XHtmlWriter();
...
}
}
The least flexible approach as this seals in the choice of writer forever.
4) Use an internal factory method...
PHP Code:
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$writer = $this->createWriter();
...
}
protected function createWriter() {
return new XhtmlWriter();
}
}
This is marginally better, because now other developers can subclass the Template and override createWriter() to return a different kind of writer. However you can only inherit once so this allows only one option per class. It also won't help if Template itself is inherited from something more complicated. Things just get too tangled. The pattern itself is called TemplateMethod or sometimes "programming by differences".
5) Pass in a factory. This is very highly factored solution and will solve even the most complex of cases...
PHP Code:
$template = new Template(new WebInfrastructure());
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$writer = $this->_infrastructure->createWriter();
...
}
}
You rarely need this much firepower, but once you are used to the idiom itis quite clean. Use this especially when you want to completely swap out the infrastructure of a system. That is you are not just changing the writer but other parts as well as a group. For example a command line infrastructure would have a TextWriter and might have getArguments() return the $argv values rather than $_REQUEST. The pattern is called the AbstractFactory when it creates families.
6) A static factory...
PHP Code:
class Xhtml {
function createWriter() {
return new XhtmlWriter();
}
}
$template = new Template(new WebInfrastructure());
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$writer = Xhtml::createWriter();
...
}
}
This is a classic case of people copying Java idioms without thinking. There are language restrictions in Java regarding the number of public classes and tricks involving the use of inner classes to avoid exposing too many interfaces. These idioms are unnecessary and unavailable in PHP. This trick buys nothing that the "new" operator doesn't give you already, other than the ability to change the delivered class by editing the code in just one place. Completely useless and yet you see it all over the place.
7) Make the object global. This is so bad I find it painful to even type in the example...
PHP Code:
global $writer;
$writer = new XhtmlWriter();
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
global $writer;
...
}
}
The chances of accidents happening heads rapidly to 100% on any project that is remotely complicated. Besides some other method overwriting the global, you have to make sure it is properly set up once only before you use it. Programers will come up with all sorts of naming conventions and practices to prevent accidents not realising that they are creating more work than if they just added some design. When the disaster occours anyway, debugging is a nightmare.
8) The Singleton pattern is a very clever way to implement a global. Basically this is a static method that returns only one instance. You might want to do this when you only want one object created ever, either because of creation cost or because it needs a memory. A database connection is a good example because it usually has to keep track of transactions. Here is the writer again. Imagine it has to send some headers once only on first use...
PHP Code:
class XhtmlWriter {
...
function getInstance() {
static $writer;
if (! isset($writer)) {
$writer = new XhtmlWriter();
}
return $writer;
}
}
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$writer = XhtmlWriter::getInstance();
...
}
}
By hiding the variable inside a "closure", here named getInstance(), you ensure it can only be accessed via that method. This ensures that it is read only and that it is correctly set up. A clever little pattern, but the static method makes this pattern very inflexible. It also makes the application difficult to test because, that single instance is impossible to reset between each test. Code that you cannot test is probably broken and the Singleton these days has a bad reputation.
9) The registry, which usually forms the basis of "dependency injection", is a major upgrade to the Singleton. Essentially the registry object is the singleton to which you add the global services...
PHP Code:
$registry = Registry::getInstance();
$registry->setWriter(new XhtmlWriter());
$template = new Template();
$template->substitute('title', 'You & me are here');
$template->paint();
class Template {
...
function paint() {
$registry = Registry::getInstance();
$writer = $registry->getWriter();
...
}
}
You have to ensure that the registry entry is set up before it is used of course. The advantages of teh registry approach is that the registry can be set up differently for each and every test in your test scripts, and that additional flexibility can be added as needed such as a lookup order for a resource. This is a complex solution to say the least.
I have probably missed a few, but that should give you enough options.
yours, Marcus
Bookmarks