As object-oriented software has evolved over the years, quite a few solutions to specific problems have also adapted and gained wide adoption. In 1995, a book called “Design Patterns: Elements of Reusable Object-Oriented Software” documented 23 patterns and started a new way to describe software that would be understandable to other developers. This book was written by four authors, Erich Gamma, Richard Helm, Ralph Johnson, and John Glissades, who are often referred to as the Gang of Four. The book is also sometimes just referred to as the Gang of Four book or just the GoF book.
When you learn how to make use of these patterns, your software will become more flexible and easier to maintain as new features are added. This episode introduces patterns and then describes the factory pattern.
The code example given in the episode describes a base class called Dungeon that makes use of a base class called Monster. There can be different types of monsters just like there can be different types of dungeons. The base Dungeon class doesn’t know what specific Monster class to create so instead it declares a virtual method called createMonster. This method could have a default implementation or it could be pure virtual. That’s up to you.
The main thing is that a specific dungeon class, let’s say a MountainCave class that derives from Dungeon, overrides the createMonster method to create some specific instance that derives from the Monster class. By deriving from the Dungeon class, you can change the type of monster that gets created. This is the factory pattern. The createMonster method is called the factory method. Listen for more or you can also read the full transcript below.
Look around at almost anything nearby and think about what similar items looked like 10 years ago. Or 20 years ago. Or more. Sure, some things haven’t changed at all since they were first invented and it’s also possible to buy things with a retro look. But most things adapt and get better and add more features. They become faster, smaller, and lighter. Even things designed to be heavy such as a hammer have changed over the years. I still remember the days when before I could use a hammer, I had to check to make sure the head was firmly attached to the handle.
Let’s say that you wanted to build a new hammer. Would you start from the very beginning and create a design that would have been modern before electricity was widely available? No. You should add your new idea to a modern design. The same thing applies to software design.
With software, though, this is even more important. Because when you release your first version, you’re going to be under pressure to get it out fast and with as few features as possible. If you’re not using well established patterns, then you’re going to have a lot more trouble adding new features for version 2.
This is because many of the patterns are not something that any of us would write on our first attempt. Especially when we’re under pressure to get something done. We’re likely to create something that looks modern but is written like it was a rock tied to a stick. And that’s one of the best things about design patterns. They can actually help speed up the process and prepare the software for future growth because each pattern is already well designed.
Recognizing opportunities for applying patterns is another area that the Design Patterns book helps because for each pattern, the authors describe the problem, the solution, and give you enough guidance to judge for yourself if the pattern will help.
There’s nothing new in the design patterns book. But what it did was bring together 23 well tested and widely applicable solutions and set an example for how other patterns should be documented. The patterns in the book have become well known by consistent names and this has helped standardize how software engineers describe solutions to common problems. If another software engineer says, “Oh, you need a factory here,” then you’ll know exactly what is meant and what needs to be done to add a factory.
It’s important to note that while patterns help you design solutions to specific problems, you need to learn them so you’ll know when to apply them in your code.
Knowing when not to apply a pattern is also something to keep in mind. When you implement a pattern, you’ll most likely end up with a bit more code than if you just went ahead with your current needs. It may not always be worth the extra steps. Documenting your decisions to use a pattern or not is usually a good idea. Just add some comments to your code about why you decided to structure your code in a certain way and why you thought a pattern was best or why you didn’t use a pattern. A year or two later when you or another developer is working with this code, the same questions will probably come up again.
I’ll explain the first pattern called a factory right after this message from our sponsor.
( Message from Sponsor )
Anytime you need to create a specific instance of one of your classes, ask yourself if this might need to change someday. Or maybe you’re designing a base class that needs to create another class but doesn’t know the exact class to create. What can you do? This is where the factory pattern can help. The factory pattern is classified as a creational pattern because it helps you create objects.
Maybe you’re wondering what would a base class do with an object if it doesn’t know how to create the object in the first place. Well, for that, I’ll just ask if you need to know all the steps necessary to bake a loaf of bread in order to eat a piece of bread? Eating food is an abstract concept that applies to food in general. It doesn’t matter what kind of food. I mean, sure it matters, but not in terms of software. This is because the base class only needs to work with the new object through an interface or through some other class. When I say that the base class doesn’t know what exact class to create, this is because there could always be some other class, maybe a concrete class, that you actually want to be created.
What you do then is define a method in the base class called CreateABC where ABC is the name of the general concept you want created. I know, this is a little vague. Let me give you a specific example.
Let’s take the adventure game example and say we have a Dungeon base class that represents the common behavior of all dungeons in the game. When the hero enters a dungeon, we need an appropriate monster to fight. But what kind of monster? Well, that depends on the type of dungeon. The Dungeon class defines a virtual method called createMonster that returns an instance of a Monster class. The Monster class is also a base class for all actual monsters.
Now, the hero at some point will enter not just any dungeon, but a specific dungeon. This dungeon will be represented by a concrete class that derives from Dungeon. Let’s say this is actually a cave in the mountains. So there’s a MountainCave class that derives from Dungeon and overrides the createMonster method to return a rock monster. It knows the exact type of monster to create and returns the new monster as the base monster class.
A different kind of dungeon, maybe some underwater tunnels, will have its own specific dungeon type that overrides the createMonster method to return a giant fish with rather large teeth.
In each of these cases, it was the specific derived classes that created the needed object. They acted as the factory. The base class just defined the factory method and called that method when it needed a new object. The base class has no idea of what the actual object type is and it just works with the object through some common interface or another base class.
This approach gives the adventure game the flexibility to create new monster types that didn’t exist in the first version of the game by adding new dungeon types in the second version.