The Factory Method pattern is useful in these situations:
When you don’t know the objects that will be used at runtime, but you’re aware of the •
common interface
When you require uniformity/localization among product creation and product assembly •
Any time you use
• new in a line of code
Example
Revisiting the Shell code from Listing 6-3, you still need to extract the creational logic that returns the appropriate instantiation of an overlay to use.
You know that including an abstraction could have assisted you in the Simple Factory. You can also use the abstraction to separate the logic and the preparation of the product returned. Doing so provides uniformity and flexibility, and saves you from having to add the assembly to each different logic implementation. You want to ensure that the logic can be easily extended, as well as modified.
You extract the Shell logic that created the overlays (see Listing 6-6) and place it for the time being in a class called OverlayFactory, as shown in Listing 6-7. In Listing 6-8, you also remove the preparation of the returned product of the factory to a class called OverlayPreparation.
Listing 6-6. Revisiting the 500-line excerpt of creational logic in Shell
private function createOverlay( overlayType : String , dataObject : * = null ) : void
{ header.disableNavigation(); currentSection.pause(); if (currentOverlay) { destroyCurrentOverlay(); } switch(overlayType) { case SectionEvent.VIDEO_OVERLAY: currentOverlay = new VideoOverlay();
case SectionEvent.PHOTO_OVERLAY: currentOverlay = new PhotoViewer(); break;
case SectionEvent.AUDIO_OVERLAY: currentOverlay = new AudioOverlay(); break;
case SectionEvent.TWITTER_OVERLAY: currentOverlay = new TwitterOverlay(); break;
case OverlayEvent.TWITTER_SUBMIT_OVERLAY: currentOverlay = new AddTweetOverlay(); break;
}
currentOverlay.y = SiteConfig.HEADER_HEIGHT;
currentOverlay.addEventListener( OverlayEvent.CLOSE_OVERLAY , handleEvent ); currentOverlay.addEventListener( OverlayEvent.INTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.OUTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.PLAY , handleEvent );
currentOverlay.addEventListener( OverlayEvent.SUBMIT_TWEET , handleEvent ); currentOverlay.addEventListener( OverlayEvent.TWITTER_SUBMIT_OVERLAY , handleEvent );
addChild( currentOverlay );
currentOverlay.updateLayout( stage.stageWidth , (stage.stageHeight – SiteConfig.FOOTER_HEIGHT - SiteConfig.HEADER_HEIGHT) ); currentOverlay.intro();
}
private function destroyCurrentOverlay() : void
{
// ... implementation not shown }
// ... truncated code
Listing 6-7. OverlayFactory encapsulates the logic required to instantiate the appropriate overlay
package {
public class OverlayFactory {
public function OverlayFactory() {
}
public function createOverlay( overlayType : String ) : Overlay {
switch(overlayType) {
case SectionEvent.VIDEO_OVERLAY: currentOverlay = new VideoOverlay(); break;
case SectionEvent.PHOTO_OVERLAY: currentOverlay = new PhotoViewer(); break;
case SectionEvent.AUDIO_OVERLAY: currentOverlay = new AudioOverlay(); break;
case SectionEvent.TWITTER_OVERLAY: currentOverlay = new TwitterOverlay(); break;
case OverlayEvent.TWITTER_SUBMIT_OVERLAY: currentOverlay = new AddTweetOverlay(); break; } return currentOverlay; } } } Listing 6-8. OverlayPreparation package {
public class OverlayPreparation {
private var factory : OverlayFactory = new OverlayFactory(); private var currentOverlay : Overlay;
public function OverlayPreparation() {
}
public function createOverlay( overlayType : String ) : Overlay {
currentOverlay = factory.createOverlay( overlayType ); currentOverlay.y = SiteConfig.HEADER_HEIGHT;
currentOverlay.addEventListener( OverlayEvent.CLOSE_OVERLAY , handleEvent ); currentOverlay.addEventListener( OverlayEvent.INTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.OUTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.PLAY , handleEvent );
currentOverlay.addEventListener( OverlayEvent.SUBMIT_TWEET , handleEvent ); currentOverlay.addEventListener( OverlayEvent.TWITTER_SUBMIT_OVERLAY , handleEvent );
currentOverlay.updateLayout( stage.stageWidth , (stage.stageHeight – SiteConfig.FOOTER_HEIGHT - SiteConfig.HEADER_HEIGHT) ); return currentOverlay; } } }
The first step in devising a common link is to note the commonalities that these two objects share. The two are, in a way, related in trying to achieve the same goals, and both depend on the product type. This dependency helps to ensure that both the assembler and the factory work toward a final product that doesn’t break the contract between the two classes.
Considering the contract between the two objects, it’s clear that you can use this method to join the two existing classes using inheritance. The common method can be refactored into an abstract class that your subclass can override (see Listing 6-9 and Listing 6-10).
Listing 6-9. AbstractOverlayFactory is the abstract class and declares the createOverlay method
package {
public class AbstractOverlayFactory {
public function createOverlay( overlayType : String ) : Overlay {
throw new IllegalOperationError( 'createOverlay must be overridden' ); return null;
} } }
Listing 6-10. OverlayFactory subclasses AbstractOverlayFactory in order to implement logic
package {
public class OverlayFactory extends AbstractOverlayFactory {
override public function createOverlay( overlayType : String ) : Overlay {
var currentOverlay : Overlay; switch(overlayType)
{
case SectionEvent.VIDEO_OVERLAY: currentOverlay = new VideoOverlay(); break;
case SectionEvent.PHOTO_OVERLAY: currentOverlay = new PhotoViewer(); break;
case SectionEvent.AUDIO_OVERLAY: currentOverlay = new AudioOverlay(); break;
case SectionEvent.TWITTER_OVERLAY: currentOverlay = new TwitterOverlay(); break;
case OverlayEvent.TWITTER_SUBMIT_OVERLAY: currentOverlay = new AddTweetOverlay(); break; } return currentOverlay; } } }
Currently you generate an abstraction that enables many subclasses to be created by extending
AbstractOverlayFactory and overriding createOverlay to implement its logic in determining the appropriate product to return. What you’ve yet to create is the means by which object preparation occurs.
The logic should have zero knowledge of how the object is prepared. Similarly, the preparation shouldn’t know how an overlay is chosen. Both of the two classes should focus on is the product’s type, because this is what maintains compatibility between the classes.
By adding the preparation to the factory abstraction, you get all the benefits without any additional issues. You can also create new subclasses with different logic implementations, and not have to recode their assembly, because only the objects—not their type—change.
Listing 6-11. AbstractOverlayFactory includes the assembly process for the returned object
package {
public class AbstractOverlayFactory {
public function makeOverlay( overlayType : String ) : Overlay {
var currentOverlay : Overlay = createOverlay( overlayType ); currentOverlay.y = SiteConfig.HEADER_HEIGHT;
currentOverlay.addEventListener( OverlayEvent.CLOSE_OVERLAY , handleEvent ); currentOverlay.addEventListener( OverlayEvent.INTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.OUTRO_COMPLETE , handleEvent ); currentOverlay.addEventListener( OverlayEvent.PLAY , handleEvent );
currentOverlay.addEventListener( OverlayEvent.SUBMIT_TWEET , handleEvent ); currentOverlay.addEventListener( OverlayEvent.TWITTER_SUBMIT_OVERLAY , handleEvent )
return currentOverlay; }
protected function createOverlay( overlayType : String ) : Overlay {
throw new IllegalOperationError( 'createOverlay must be overridden' ) return null
} } }
To ensure that preparation is used and not bypassed, AbstractOverlayFactory removes createOverlay as part of its interface so that only the superclass can call it. As a protected method, each subclass overrides createOverlay with its chosen implementation; and preparation is ensured because that process is in the additional method makeOverlay. The makeOverlay method has become the factory method that any client may message; in return, the client gets a product. Although this example returns the assembled product to an external client, this isn’t always the case. Even the abstraction can be the client of the product, as well as the invoker of the factory method.
makeOverlay delegates the responsibility of creating an overlay to the logic in an encapsulated creator. Because the subclass and the abstract class both rely on the product type, the union between product preparation and creation is localized into one object, while maintaining great cohesion for maintenance.
What results from the factory method is a cohesive set of classes that localize common behavior in a product to be used in a system. By using inheritance, you can partition preparation from instantiation while abstracting the product from the client.
Localization and the union between preparation and creation are so well contained that it appears as if all you’re instantiating is a class that implements the logic for object instantiation.
FAQ
If the subclass and the superclass form the factory, and both know about the abstract product, •
why separate the logic from the assembly?
You should keep the code localized for ease of maintenance. However, by injecting the means to package and prepare a product into the logic, you remove the flexibility to add or remove new products without physical maintenance of the object. The separation offers flexibility through its use of inheritance. If each product differs in the way it needs to be assembled or prepared, should a subclass have •
more control than the abstract over the means of preparing each object?
The factory method focuses on one abstract type to return. If the assembly of a particular object varies from other objects of its type, it may be unabstracted. If this isn’t the case, you can use the Builder pattern with the factory method. However, a subclass shouldn’t have any knowledge about the preparation. If the subclass is a smarter and more specialized form of the superclass, shouldn’t you be able •
to modify the methods declared in the abstract if they need to be more specific?
As stated in the previous answer, a subclass shouldn’t have any knowledge about the preparation of the product. If each concrete class could modify the logic and the preparation, the hierarchy could have infinite levels. This would be poor practice, because if any change occurred in the lower levels of a subclass, an error could present itself in any subclass.
Can a factory build more than one product? •
Yes, a factory can return more than one product. Ensure that each factory method is named appropriately for the product being returned. If the products need to reside together as a set, consider the next pattern: Abstract Factory.