The patterns that will be discussed first, following the original GoF Design Patterns book approach, are the creational ones. Creating object graphs is an essential skill in object-oriented development and correctly addressing the bootstrap of the application and the object instantiation is part of the programming's complexity.
The first pattern we will introduce is the Abstract Factory one, also known as Provider or simply as Factory, in its variant which does not include explicit interfaces.
The major problem that creational patterns try to solve is that objects need collaborators: we often pass them in the constructor of a Client class to aid decoupling, as every class should know only what it really needs to get its job done. With the verb know I mean that they just know that the other part exist at all.
The fundamental principle of asking for things (Dependency Injection) substitutes the naive approach of looking for things or creating them.
A Client is simply the generic name of an high-level class which makes use of the collaborators, and occurs frequently in the design patterns Ubiquitous Language. These generic names are known as participants in a pattern, and allow developers to discuss a pattern without referring to a particular implementation.
Dependency Injection prescribes to ask for collaborators in the constructor of a Client, or via setters that accepts the collaborator as a parameter to store it for later use. Most of the times collaborators are created at the startup of the application/script, before their Client, to satisfy the dependency.
The fallacy here (which is not really a fallacy, since it works most of the time) is evident when the collaborators assume shorter lifecycles. Often objects are created as a result of business logic (a Post or Forum instance, for example, in the middle of a php script), and by the way with a shorter lifecycle of the object that creates them. As another example, they can be lazily created only when necessary.
In php's case, lazy creation is mandatory because there is no need to create all kinds of helper objects on every single http request. You certainly do not expect to define all your buttons, links and inputs at the startup in a single object graph: their creation is regulated by an analysis of the request parameters and of the database state.
Though, you may want to govern which kind of objects are created: if the names of the ConcreteProduct classes are hardcoded in the Client, you lose the flexibility of Dependency Injection, which provide advantages like real unit testing and decoupled design.
The AbstractFactory solution consists in encapsulating the creation process behind an AbstractFactory interface, which may be implemented by how many different ConcreteFactory classes are needed. One of this ConcreteFactory is then injected in the Client that will call its methods to create the objects, when ready.
Pattern participants:
- AbstractProduct: the interface for the collaborator
- ConcreteProduct: an implementation of the collaborator
- AbstractFactory: the interface for the factory that builds an AbstractProduct
- ConcreteFactory: an implementation of the AbstractFactory, to be injected in the Client
- Client: the final user class of the collaborator
<?php /** * SECTION 1: a WidgetHelper interface and two different implementations. * Normally we would inject the chosen WidgetHelper to the Client class, but * creating all possible helpers renders the constructor enormous, whilst we * are not even sure that they would be actually used. * * This class purpose is to generate blinking text in spite of all * usability recommendations. This is the AbstractProduct. */ interface WidgetHelper { /** * @return string */ public function generateHtml($text); } /** * Implementation that generates html tied to a javascript library. * This is one ConcreteProduct. */ class JavascriptWidgetHelper implements WidgetHelper { public function generateHtml($text) { return '<div dojoType="...">' . $text . '</div>'; } } /** * Implementation that generates html that loads a flash object. * This is one ConcreteProduct. */ class FlashWidgetHelper implements WidgetHelper { public function generateHtml($text) { return '<object> <param name="text">' . $text . '</param> </object>'; } } /** * SECTION 2: since we are not going to pass the instances of WidgetHelper to * the Client class (because they do not exist yet), we need an interface * for the creator of these WidgetHelpers, which results in an Abstract Factory. * This is the collaborator which would be injected in the client. */ interface WidgetHelperAbstractFactory { /** * @return Widget */ public function createWidgetHelper(); } /** * First implementation: creates a Javascript-based helper. * This is one ConcreteFactory. */ class JavascriptHelperFactory implements WidgetHelperAbstractFactory { public function createWidgetHelper() { return new JavascriptWidgetHelper(); } } /** * Second implementation: creates a Flash-based helper. * This is one ConcreteFactory. */ class FlashHelperFactory implements WidgetHelperAbstractFactory { public function createWidgetHelper() { return new FlashWidgetHelper(); } } /** * Third implementation: creates a random type of helper. * Note that commonly the WidgetHelperAbstractFactory interface will require * methods to create all the helpers needed. It's up to the single * ConcreteFactory implementation to instantiate a family of objects * (flash|javascript html bindings generators) or another, * or even a mixture of different families or whatever. * Dependency Injection containers take this approach to the extreme, * providing automatically configurable factories for every * concrete class. */ class RandomHelperFactory implements WidgetHelperAbstractFactory { public function createWidgetHelper() { srand(); if (rand() > 0.5) { return new JavascriptWidgetHelper(); } else { return new FlashWidgetHelper(); } } } /** * SECTION 3: a Client class that uses an AbstractFactory to create Widget * instances whenever it wants. * Note that this class only depends on abstractions (AstractFactory and its * AbstractProduct). However, since php has no compile-time dependencies, * an interface for the Products or the Factories is not mandatory. */ class LoginPage { private $_widgetFactory; public function __construct(WidgetHelperAbstractFactory $factory) { $this->_widgetHelperFactory = $factory; } public function render() { $userId = uniqid('User '); // insert all the logic needed here... if (true or $complexBusinessLogicRules) { $helper = $this->_widgetHelperFactory->createWidgetHelper(); return $helper->generateHtml("Welcome, $userId"); } } } $page = new LoginPage(new FlashHelperFactory()); echo $page->render(), "\n"; $page = new LoginPage(new JavascriptHelperFactory()); echo $page->render(), "\n";Often factories in php applications are applications of this pattern without the abstract trait, with a developed dependency towards a ConcreteFactory. With the same pragmatism, different kind of collaborators which are highly related can be placed in the same ConcreteFactory, by adding other creation methods.
I hope you now grasp how to build objects at the right time in your application, as lazily as you want, without depending on concrete classes.
Tomorrow we will discuss another creational pattern, the Builder one.
Don't forget to comment if you like the post or you feel something is missing. Feedback can drive the evolution of this series of articles.
Good start. Curious about the things to come.
ReplyDeleteThe word "Abstract" in the interface classnames could be a bit confusing since it's often used in abstract classes. Maybe a short note clarifying this would be helpful (especially for novice readers).
I just kept the GoF terminology: the name illustrates that Client depends only on abstractions. :)
ReplyDeleteVery nice, do you also use this technique to inject dependencies in zf action controllers? If so how do you inject the abstract factory? And is it clean to use an abstract factory to create different (unrelated) objects just for the sake of providing the client with dependencies.
ReplyDeleteCurrently the architecture does not permit to inject anything in controllers; what can you do is instantiating a factory in a bootstrap resource. It is often cleaner to inject directly the dependencies (but since the controllers should have an empty-argument constructor a factory is the only choice).
ReplyDeleteNice article, waiting to read the next one in the series!
ReplyDeleteYour print stylesheet breaks on Firefox on Ubuntu. There is one page of text then the menu and that's it.
ReplyDeleteBummer for printing to read later.
If I need to decide at runtime which factory to create, what will be the best approach, do I have to inject this decision maker in my controller?
ReplyDeleteYes, an higher level factory will create the controller and inject the lower level factory it needs to perform its work.
ReplyDeleteDid you mean private $_widgetFactory; to actually be private $_widgetHelperFactory; as the first is never used and the second is used but never instantiated.
ReplyDelete