Search:
|
Access:
» How I Learned To Love The Factory PatternRelated categories: Tom JonesViewed: 1714 | Article date: 2006-05-13 17:36:48 We create objects and we use objects. We sometimes even free objects! But in the end, that´s all we do with our object orientated programing language. Although the distinction between freeing and using is quite easy, the line between the creation of objects and their use is blurred. "Wait", you might say, "what about the constructor that comes with every object? Isn't that the place for the creation code and everything else is just using that object?"
We create objects and we use objects. We sometimes even free objects! But in the end, that's all we do with our object orientated programing language. Although the distinction between freeing and using is quite easy, the line between the creation of objects and their use is blurred. "Wait", you might say, "what about the constructor that comes with every object? Isn't that the place for the creational code and everything else is just using that object?"
About the authorTom Jones is working as a software architect at ISOware GmbH, Munich. Contact him at tom.jones@isoware.com You're right, but just in theory. If you're honest, you've written code like this a thousand times: o := TSomeObject.Create ("Hello"); o.SomeProp := "OK"; The code snippet can be seen as a complete initialization of an object, or as the creation and use of an object. This may sound fussy, but the point is that it's good to keep this difference in mind - not in a small 1000-lines project, but in a big one where good architecture is vital for success. The Factory Pattern can't make this design decision for you, but it will clear the path for a tested and well-known approach that can be used in many situations, like - for example - the one above. Here are the main advantages:
To explain how the Factory Pattern (and the Abstract Factory Pattern, to be precise) works, I will use the rudimentary game engine, shown in Listing 1. It is by far not a running program, but will serve the purpose to show how a "not so flexible" design can be changed into a more flexible one. The source code examples are written in Delphi, but any programmer should pick it up quickly: When you read .Create, think of the new-operator and don't get confused by the begin - end - blocks, they're just structural blocks ({ ... } - expression in Java or C++). Classnames in Delphi usually begin with a 'T' (for 'type'), interfaces with a 'I'. Example: Sprites and PlatformsTake a look at Listing 1. It shows the game's level logic that creates one big monster and one small monster after 25 seconds. Two minutes later a big blue monster shows up (just ignore the question how this could ever be a working game. It's all about principles!). We're using the classes TBigMonsterSprite and TSmallMonsterSprite for the sprites and the game should run on a standard Windows system. Listing 1 already shows a tight coupling between the game logic and the creation of the sprite objects. Now: what if we have to add a new sprite with a slightly different behaviour, for example for a new level? What if we have to swap out all the sprite objects because your company decided that they have to move into the games-market for mobile devices? We can see that Listing 1 isn't ready for this kind of requirement! Listing 1. Tlevel1.Run procedure
procedure TLevel1.Run( TimeElapsedInSec: integer ); To avoid this embarrassing situation, we have to find out what parts of the code will be likely to change in the future. In our case, we have sprites that represent big monsters in the desktop version of the game as well as the same sprites for the mobile platform. In addition, we need to create sprites that show the small monsters on both platforms. To start with, we extract an interface for all the different sprite classes, which is shown in Listing 2, and change the code that creates the code (the "client") so it uses the sprite interface. After that we create the new methods CreateBigMonsterSprite and CreateSmallMonsterSprite, which return new ISprite objects, according to their names. We then redirect the calls to TBigMonsterSprite.Create and TSmallMonsterSprite.Create to our new methods (Listing 3). Listing 2. An interface for all the different sprite classes
type Listing 3. New method
procedure TLevel1.CreateBigMonsterSprite: ISprite; Now the coupling between game level and sprites is already less tight, thanks to the sprite interface and the two methods called "factory methods". If we want to, we can now switch sprites for different platforms by adding some conditional statements to CreateBigMonsterSprite and CreateSmallMonsterSprite, but that's not the way the Factory Pattern wants us to do it. Instead, the Factory Pattern uses inheritance to create its different "products": Figure 1 and Listing 4 show how we must change our code to get full extensibility in supporting our game on many platforms. The two factory methods are now abstract and will be implemented in the concrete classes TLevel1Desktop and TLevel1Mobile. The level's game logic will remain in the static method Run of TLevel1 and is now completely platform-independent. The decision, which platform to use lies in the code of the main game class where the concrete level class is instantiated. As we want to support two platforms we now need four sprite classes, two for each platform.
|
|
Copyright C 2006 by Software Developer's Journal. All rights reserved.






SDJ Users:
Shopping Cart









