Reviewing Game Programming Patterns inspired me to read the classic Design Patterns, Elements of Reusable Object-Oriented Software. My guess going in was that it would be more complete but not as nice to read, that’s pretty much how it was. However there were some unique patterns in each book so, if you like patterns, it may well help to read both.

Presentation

Most chapters are dedicated to individual patterns. Each one gets and description and an example to give you some motivation for it’s use. It discusses the consequences of using the pattern. This gives useful insight in how it might fit with specific use cases. Often it’s the case that in order to reduce one type of complexity you have to increase another type. You might reduce build time coupling between systems but move some of that complexity to runtime. That could mean that debugging the new system is more complicated. It has an example situation with some code in C++ or sometimes Smalltalk. (The examples aren’t as exciting as ones using video games.) The use of Smalltalk seems a bit niche, maybe it made more sense when it was published.

The beginning of the book is much harder work. There’s an introduction to the idea of patterns, a brief summary of the patterns the rest of the book will cover and the sort of tools that will be used to make them, composition and inheritance. The problem comes with a case study of a what-you-see-is-what-you-get editor. It looks at seven design problems and how design patterns can be brought to bear to solve these. We haven’t read about the patterns yet but they all get thrown at us at once. I think this is meant to wet out appetite but it felt more overwhelming. Maybe it would have been better at the end of the book.

Content overview

  • Creational pattern
    • Abstract factory: Creates related objects without having to know their specific type.
    • Builder: Separates construction from representation so different specific types can be created.
    • Factory method: Allows a derived object to decide which class to create.
    • Prototype: Replaces construction with cloning of an existing object.
    • Singleton: Ensure only a single class instance can exist.
  • Structural Patterns
    • Adapter: Convert one class interface into another interface.
    • Bridge: Separate interface from implementation so the later can be replaced.
    • Composite: Using tree structure allows objects and compositions of objects to be treated uniformly.
    • Decorator: Extend the behaviour of an object without changing it.
    • Facade: Create a uniform interface to an collection of existing systems.
    • Flyweight: Efficiently support many similar objects.
    • Proxy: Provide a surrogate access to another less available object.
  • Behavioural Patterns
    • Chain of responsibility: Allow an event multiple chances to be processed.
    • Command: An object that represents an action.
    • Interpreter: Process sequences of tokens into a series of actions.
    • Iterator: Allow access over an aggregate object without knowing it’s representation.
    • Mediator: Encapsulate the relationship between a number of objects to reduce coupling between them.
    • Memento: Store an object’s state to re-use later.
    • Observer: Allow many objects to respond an event without the initiator having to know about them.
    • State: Allows an object to switch between behaviour patterns.
    • Strategy: Encapsulate a family of algorithms so that clients can use any one.
    • Template method: Define an algorithm but allow some steps to be customised to specific uses.
    • Visitor: An operation to be performed across the elements in an aggregate structure.

Embedding patterns

For me looking through this list one standout was the iterator pattern. Iterators are just iterators, part of the standard template library. Do they really need their own chapter? All ideas have a history and this book was written back in 1995. Back in the day you accessed simple containers using an index and complex contains would have something custom. Then people came up with iterators so you could treat containers in a uniform way. It looks like they made their way into C++98 standard for everyone to use.

What starts out as an idea or a pattern can be implemented in a language or a library and become much easier to use for everyone. It doesn’t stop there. Nowadays I tend to avoid using iterators directly but instead use a range-based for loop. It hides away some of the details and leaves less room for mistakes. Actually this feels like the application of the visitor pattern. The body of the for loop is being applied across the collection without any additional fuss.

Template methods are, of course, closely related to templates. An algorithm can be best optimised for a specific type or operation if the code is specifically compiled for that type. In C++ that does often require template code to be kept header files, which is annoying, but that’s more down to the languages long history and how much slower computers use to be. Using a language that requires a specific type to be cast to a general object and back to use a collection seems very clunky to program and it’s going to be slower.

Lambda expressions can be easily used to implement the command pattern. Almost any collection of code and data can be frozen to use again later on. The convenience doesn’t guarantee success. Part of the problem with, say, the undo-redo buffer is the creation and destruction of objects. If you lambda expression is pointing to things that are no longer part of the document or have even been destroyed that’s not going to work.

What other patterns have been incorporated into language and libraries:

  • Prototype: JavaScript is prototype based cloning existing objects rather than constructing them from scratch.
  • Proxy: Objective-C provides messaging forwarding which allows objects to delegate the task of processing some messages to another object.
  • Chain of responsibility: This is used a lot but typically as part of a windows event handling system.
  • Memento: Boost serialisation is often used for longer term storage in files but might be better suited for temporary storage here, that way you don’t have to worry about versioning problems.
  • Observer: C# handles these directly as events whereas in C++ you have to make your own.

Embedding your design pattern gives the advantage that it is accessible to everyone. It’s a standard that people can learn and systems can use it to interoperate. A disadvantage is that it’s a specific implementation of that design pattern. Quirks or problems with that implementation many not be impossible to fix but might be much harder.

I think my biggest wish for C++ would be to get extension objects. It’s not covered in this book but is available in, say, C# as extension methods. It allows a class to be extended with additional methods without having to change the original class. It’s a great for enhancing existing systems but keeping a uniform access to the system.

On balance

I don’t think I’ll be keeping this book. It was useful to see where the design pattern space has come from. However in the future I get can revisit the patterns with online resources or similar. I would still like to skim through some other design pattern resources and see if they present any other options.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *