|
Friday, 28 July 2006 |
In object oriented programming, object creation - also known as instantiation - is an implied requirement. Objects must at some point be created for use. Obviously, creating objects is not a difficult task and most languages, PHP included, have simple and intuitive syntax for doing so.
When developing larger, more complex systems though, object creation can become difficult. There are situations where different objects may need to be created based on different conditions or based on the context of the object creating it. Creating objects of concrete types explicitly in code can make these situations a nightmare when it comes time to make revisions and additions. When a new class is introduced, you get to follow a trail of code and commence the hours of debugging that will inevitably follow such an endeavor. This is where design patterns come in.
The Factory Method pattern defines an interface for object creation but defers the actual instantiation to subclasses. Take, for example, an application that processes Electronic Funds Transfers (ETFs). There are numerous types of ETFs including virtual check, credit card, wire transfer and so on. Using a non-pattern based approach, the application code requesting an ETF object would need to know precisely what subclass of ETF is needed, and it would need to know the context in which that type of ETF is requested. We would end up with code looking something like this:
switch ($etfType) { case ETF_VIRTUALCHECK : $etf = new VirtualCheck(); $etf->processCheck(); break; case ETF_CREDITCARD : $etf = new CreditCard(); $etf->chargeCard(); break; case ETF_WIRETRANSFER : $etf = new WireTransfer(); $etf->chargeCard(); break; }
Any time we want to add another ETF, we would have to manually update this switch statement anywhere it appeared. We would also have to update any other conditional code that appears. The CreditCard class hinted at above offers the same problem as the ETF itself in that each credit card type (VISA, MasterCard, AMEX) has its own validation scheme and many types have different numerical formats. We would see a similar switch statement to determine what type of credit card we were dealing with either in the CreditCard class constructor or in the switch statement above creates the CreditCard object.
By implementing the Factory Method, we code our application so it only expects a class that conforms to an interface - that is, it has certain methods and properties that can be used to submit an ETF and check whether it failed or succeeded. This promotes loose coupling in the application because you are not binding a concrete subclass to application code. The result is a great increase in flexibility and maintainability. It is easy to add new ETF subclasses and implement them because you are not hard coding the application to expect a specific subclass, just a class with a specific interface. Look at this example.
class ETF { var $data; function ETF($data) { $this->data = $data; } function process() {} function getResult() {} }
class VirtualCheck extends ETF {} class WireTransfer extends ETF {}
class ETFFactory { function createETF($data) { switch ($data['etfType']) { case ETF_VIRTUALCHECK : return new VirtualCheck($data);; case ETF_WIRETRANSFER : return new WireTransfer($data); default : return new ETF($data); } } }
$data = $_POST; $etf = ETFFactory::createETF($data); $etf->process();
This is a crude implementation but the intent should be clear. Assume the contents of $_POST represent everything you need for the type of ETF that is happening, including an 'etfType' that says what sort of ETF you are using. This would come from the user making a selection in a form and filling out the correct information. This implementation provides numerous advantages over the first.
1. Data validation can be left entirely to the subclasses and occur without interaction from calling code.
2. Calling code only needs to know of one way to get an ETF object.
3. Creation logic is encapsulated - the ETFFactory decides what concrete class to create based on the contents of $data['etfType']. Calling application code knows nothing of concrete subclasses of ETF.
4. If special measures need to be taken, such as creating a specific type of credit card object, this can take place in one location without the calling application code being involved.
Using this approach consolidates creation logic in a single class - ETFFactory. This eliminates duplication in code when an object is created in multiple locations. With the first method, if a class name is changed or a new ETF class is added, we have to modify the ETF creation code everywhere it appears in our application. With the Factory Method implementation, this is not the case. Our calling application code knows of only ETFFactory and ETF. Subclasses of ETF provide the required specialized behaviors to process themselves in the appropriate way but the calling code needs only to use the process() method.
Conclusion
The Factory Method pattern teaches us how to use methods for object creation rather than directly instantiating objects through client code. This lets the factory method itself do any work it needs to do in creating the object - work that could involve contextual considerations or initializing certain resources in the object. One of the most important concepts in good object oriented design is "design to an interface, not an implementation." What this means is that you should code your application to expect standard sets of object behaviors but not to expect specific object types. This results in a system of objects that know as little as possible about one another and, so long as the interfaces to these classes do not change, the internals of the classes can vary without adversely affecting other classes in the system.
|