As an amateur chess player I occasionally watch videos on the subject on youtube. Just as it happens I watched one old video of GM Ben Finegold on isolated queen pawn positions the other day. He started with explaining that most people don't like such positions, because beginners tend to be dogmatic. They are taught a number of rules like ‘isolated pawns are bad’, ‘knights before bishops’ and so on, and they like to stick to the rules without regard to context. So that is when it struck me, we tend do the same in programming, especially true in the case of people with limited experience. That rigid thinking however doesn't do well neither in chess nor in C++.
My favorite object to rant about these days is the beloved by beginners and advanced alike “design patterns” pattern. This was all started by the famous book by the same name written by the so called “gang of four”[1]. I, being pretty active the last year in the Qt forums, often encounter questions on the topic of design patterns from other users. So I decided to write this very post, where I hope the shed some light on the issue.
As programmers we often encounter similar problems as work progresses along which are solved pretty much the same way, and as humans we love to generalize. So that is the whole premise behind the aforementioned book, to show that a set of similar problems have a corresponding set of “good” solutions. This, of course, is generally true, but many people, especially the ones well conditioned to rigid thinking and/or those with limited experience, are ready to jump overboard and just apply it to every situation, always. So in reality this creates a very profound problem, not only are some patterns inapplicable to certain contexts, but people tend to bend over backwards in their wish to apply them, thus creating a vile mess in the process.
The original ideas were posed for Java, and while it pains me to say it constantly, C++ is not Java. Some of the proposed solutions are inappropriate to be applied directly to C++, or become much more complex if one tries to adapt them. This naturally stems from the fact that C++ is a lower-level language, albeit still object oriented, and as such has a lot of peculiarities that are not relevant to other languages.
A typical example of misunderstanding is the singleton pattern, where only one instance of an object is created, initialized and used. This pattern (or as I lovingly call it: “The singleton antipattern”) has gained widespread use for no good reason. The idea behind it is supposedly to restrict the programmer to using a single instance of a given class. There are a number of pitfalls with this, however:
- Having a singleton class imposes that you have only one object of that class, conversely needing one object does not sum up to requiring the class to be singleton(ian). If you need one instance of something, just create one!
- The singleton creates extremely tight coupling between the classes that use it. Think a C global variable. In fact the singleton is just that – a global variable dressed in shiny clothes (i.e. it has methods). The same results one can get by using a number of global functions that make use of a global variable.
- As a consequence of 1 and 2, the singleton introduces an application global state, which might be hard to manage depending on the context.
- A C++ specific problem is that most singleton implementations leak memory. Java has no such problem as the memory is managed by its virtual machine, in C++ this may be problematic.
- A lazy initialization singleton implementation must be explicitly made thread-safe in case the initialization might be done concurrently.
Other patterns described in the book and on the internet also suffer similarly and the reason is: there is no one-size-fits-all in programming.
Don't be dogmatic, evaluate the context level-headedly and only then settle on a solution.
[1] Gamma, E., Helm, R., Johnson, R., Vlissides, J., Design Patterns - Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.