Patterns are not the only way of formalizing metaprogramming concepts. Patterns address specific problems, but sometimes we would like to express ideas that are not problem specific. Such formalizations are called principles. If they are related to program design, we call them, quite obviously, design principles.
Principles provide a view of a problem that is complementary to patterns. They don't give specific instructions on how to solve problems but rather instruct us how to write good code. A good programmer should, therefore, know both design patterns and design principles by heart. An excellent programmer, of course, also knows when to use patterns and principles and when to ignore them, but that's another story. You can only get such a level of knowledge by practicing programming for a long time.
Still, everyone has to start somewhere, and at the beginning, it is advantageous to know well-known and commonly appreciated principles. I'll finish this chapter with a short review of the most important design principles.
The most important principle of OOP is undoubtedly SOLID. It covers five ideas that are a subset of many principles promoted by software engineer and author Robert C Martin (you may know him as Dr. Bob). SOLID is an acronym in which each letter represents one of the following principles:
- Single responsibility principle: This principle states that a class should only have one responsibility. It goes hand-in-hand with software decomposition. If we cannot nicely decompose software implementation into components, it will also be hard to implement classes with only one responsibility. Taking care of the single responsibility principle helps prevent the blob anti-pattern.
- Open/closed principle: This principle states that software entities should be open for extensions but closed for modification. In other words, a module (class) should be extensible (open for extensions) without having to modify its source code (closed for modification). This extensibility is usually achieved either with the careful use of object-oriented principles (inheritance, virtual functions) or delegation (for example, providing a custom comparison function to a sorting method).
- Liskov substitution principle: Introduced in 1987 by Barbara Liskov, this tells us that the program should continue to work correctly if we substitute one object with its sub-type (a derived class, for example). This principle requires that a derived class cannot have more strict requirements than its base class (the preconditions cannot be stronger in a derived class) and that it cannot give weaker guarantees about output data (post-conditions cannot be weaker).
- Interface segregation principle: This principle merely states that multiple interfaces with a specific functionality are better than one general-purpose interface. An interface segregation principle makes the system more decoupled and easier to refactor.
- Dependency inversion principle: The last SOLID principle states that you should depend on abstraction and not on the concrete implementation. When you use a class, you should only depend on the interface that is exposed and not on the specific implementation. This principle is frequently used to refer to programming for an interface, not for an implementation.
We could jokingly say that the SOLID principle doesn't respect its own rules. By the interface segregation principle, we would expect to read about five different principles and not one larger set. Other design principles in this chapter are simpler and cover only one idea.
The don't repeat yourself principle (DRY), states that every piece of knowledge should have a single representation within a system. In other words, you should implement each small part of functionality as one method, and not use copy and paste.
In practice, such detailed decomposition will also cause problems. We will usually use a function from multiple places in the code. If that shared function changes behavior, we have to explicitly check all call sites (all places that use it) to see whether the new functionality is indeed required and desired at that place. Still, this is much better than the mess introduced by copy and paste programming.
The KISS principle came into programming from the US Navy. It states that systems are more reliable when they are kept simple. Although many sources say that KISS stands for keep it simple, stupid, the original author described it as keep it simple stupid. In other words, it is not that the engineer is stupid, but that the implementation should be simple stupid, or as trivial as possible.
This principle goes hand in hand with another idea taken from the extreme programming world—YAGNI. Meaning you ain't gonna need it, this acronym teaches us to only implement parts of code that are actually needed. If you try to foresee what will be needed in the future and write it in advance, you will, in many cases, just lose time, as either you'll be mistaken or the software specification will change.
Both KISS and YAGNI require frequent refactoring when software specification is updated, so it is helpful if you know the refactoring tools in Delphi or use a software add-on, such as MMX Code Explorer.