![](/user_photo/_userpic.png)
- •Table of Contents
- •About the Author
- •About the Technical Reviewer
- •Acknowledgments
- •Software Entropy
- •Clean Code
- •C++11: The Beginning of a New Era
- •Who This Book Is For
- •Conventions Used in This Book
- •Sidebars
- •Notes, Tips, and Warnings
- •Code Samples
- •Coding Style
- •C++ Core Guidelines
- •Companion Website and Source Code Repository
- •UML Diagrams
- •The Need for Testing
- •Unit Tests
- •What About QA?
- •Rules for Good Unit Tests
- •Test Code Quality
- •Unit Test Naming
- •Unit Test Independence
- •One Assertion per Test
- •Independent Initialization of Unit Test Environments
- •Exclude Getters and Setters
- •Exclude Third-Party Code
- •Exclude External Systems
- •What Do We Do with the Database?
- •Don’t Mix Test Code with Production Code
- •Tests Must Run Fast
- •How Do You Find a Test’s Input Data?
- •Equivalence Partitioning
- •Boundary Value Analysis
- •Test Doubles (Fake Objects)
- •What Is a Principle?
- •KISS
- •YAGNI
- •It’s About Knowledge!
- •Building Abstractions Is Sometimes Hard
- •Information Hiding
- •Strong Cohesion
- •Loose Coupling
- •Be Careful with Optimizations
- •Principle of Least Astonishment (PLA)
- •The Boy Scout Rule
- •Collective Code Ownership
- •Good Names
- •Names Should Be Self-Explanatory
- •Use Names from the Domain
- •Choose Names at an Appropriate Level of Abstraction
- •Avoid Redundancy When Choosing a Name
- •Avoid Cryptic Abbreviations
- •Avoid Hungarian Notation and Prefixes
- •Avoid Using the Same Name for Different Purposes
- •Comments
- •Let the Code Tell the Story
- •Do Not Comment Obvious Things
- •Don’t Disable Code with Comments
- •Don’t Write Block Comments
- •Don’t Use Comments to Substitute Version Control
- •The Rare Cases Where Comments Are Useful
- •Documentation Generation from Source Code
- •Functions
- •One Thing, No More!
- •Let Them Be Small
- •“But the Call Time Overhead!”
- •Function Naming
- •Use Intention-Revealing Names
- •Parameters and Return Values
- •Avoid Flag Parameters
- •Avoid Output Parameters
- •Don’t Pass or Return 0 (NULL, nullptr)
- •Strategies for Avoiding Regular Pointers
- •Choose simple object construction on the stack instead of on the heap
- •In a function’s argument list, use (const) references instead of pointers
- •If it is inevitable to deal with a pointer to a resource, use a smart one
- •If an API returns a raw pointer...
- •The Power of const Correctness
- •About Old C-Style in C++ Projects
- •Choose C++ Strings and Streams over Old C-Style char*
- •Use C++ Casts Instead of Old C-Style Casts
- •Avoid Macros
- •Managing Resources
- •Resource Acquisition Is Initialization (RAII)
- •Smart Pointers
- •Unique Ownership with std::unique_ptr<T>
- •Shared Ownership with std::shared_ptr<T>
- •No Ownership, but Secure Access with std::weak_ptr<T>
- •Atomic Smart Pointers
- •Avoid Explicit New and Delete
- •Managing Proprietary Resources
- •We Like to Move It
- •What Are Move Semantics?
- •The Matter with Those lvalues and rvalues
- •rvalue References
- •Don’t Enforce Move Everywhere
- •The Rule of Zero
- •The Compiler Is Your Colleague
- •Automatic Type Deduction
- •Computations During Compile Time
- •Variable Templates
- •Don’t Allow Undefined Behavior
- •Type-Rich Programming
- •Know Your Libraries
- •Take Advantage of <algorithm>
- •Easier Parallelization of Algorithms Since C++17
- •Sorting and Output of a Container
- •More Convenience with Ranges
- •Non-Owning Ranges with Views
- •Comparing Two Sequences
- •Take Advantage of Boost
- •More Libraries That You Should Know About
- •Proper Exception and Error Handling
- •Prevention Is Better Than Aftercare
- •No Exception Safety
- •Basic Exception Safety
- •Strong Exception Safety
- •The No-Throw Guarantee
- •An Exception Is an Exception, Literally!
- •If You Can’t Recover, Get Out Quickly
- •Define User-Specific Exception Types
- •Throw by Value, Catch by const Reference
- •Pay Attention to the Correct Order of Catch Clauses
- •Interface Design
- •Attributes
- •noreturn (since C++11)
- •deprecated (since C++14)
- •nodiscard (since C++17)
- •maybe_unused (since C++17)
- •Concepts: Requirements for Template Arguments
- •The Basics of Modularization
- •Criteria for Finding Modules
- •Focus on the Domain of Your Software
- •Abstraction
- •Choose a Hierarchical Decomposition
- •Single Responsibility Principle (SRP)
- •Single Level of Abstraction (SLA)
- •The Whole Enchilada
- •Object-Orientation
- •Object-Oriented Thinking
- •Principles for Good Class Design
- •Keep Classes Small
- •Open-Closed Principle (OCP)
- •A Short Comparison of Type Erasure Techniques
- •Liskov Substitution Principle (LSP)
- •The Square-Rectangle Dilemma
- •Favor Composition over Inheritance
- •Interface Segregation Principle (ISP)
- •Acyclic Dependency Principle
- •Dependency Inversion Principle (DIP)
- •Don’t Talk to Strangers (The Law of Demeter)
- •Avoid Anemic Classes
- •Tell, Don’t Ask!
- •Avoid Static Class Members
- •Modules
- •The Drawbacks of #include
- •Three Options for Using Modules
- •Include Translation
- •Header Importation
- •Module Importation
- •Separating Interface and Implementation
- •The Impact of Modules
- •What Is Functional Programming?
- •What Is a Function?
- •Pure vs Impure Functions
- •Functional Programming in Modern C++
- •Functional Programming with C++ Templates
- •Function-Like Objects (Functors)
- •Generator
- •Unary Function
- •Predicate
- •Binary Functors
- •Binders and Function Wrappers
- •Lambda Expressions
- •Generic Lambda Expressions (C++14)
- •Lambda Templates (C++20)
- •Higher-Order Functions
- •Map, Filter, and Reduce
- •Filter
- •Reduce (Fold)
- •Fold Expressions in C++17
- •Pipelining with Range Adaptors (C++20)
- •Clean Code in Functional Programming
- •The Drawbacks of Plain Old Unit Testing (POUT)
- •Test-Driven Development as a Game Changer
- •The Workflow of TDD
- •TDD by Example: The Roman Numerals Code Kata
- •Preparations
- •The First Test
- •The Second Test
- •The Third Test and the Tidying Afterward
- •More Sophisticated Tests with a Custom Assertion
- •It’s Time to Clean Up Again
- •Approaching the Finish Line
- •Done!
- •The Advantages of TDD
- •When We Should Not Use TDD
- •TDD Is Not a Replacement for Code Reviews
- •Design Principles vs Design Patterns
- •Some Patterns and When to Use Them
- •Dependency Injection (DI)
- •The Singleton Anti-Pattern
- •Dependency Injection to the Rescue
- •Adapter
- •Strategy
- •Command
- •Command Processor
- •Composite
- •Observer
- •Factories
- •Simple Factory
- •Facade
- •The Money Class
- •Special Case Object (Null Object)
- •What Is an Idiom?
- •Some Useful C++ Idioms
- •The Power of Immutability
- •Substitution Failure Is Not an Error (SFINAE)
- •The Copy-and-Swap Idiom
- •Pointer to Implementation (PIMPL)
- •Structural Modeling
- •Component
- •Interface
- •Association
- •Generalization
- •Dependency
- •Template and Template Binding
- •Behavioral Modeling
- •Activity Diagram
- •Action
- •Control Flow Edge
- •Other Activity Nodes
- •Sequence Diagram
- •Lifeline
- •Message
- •State Diagram
- •State
- •Transitions
- •External Transitions
- •Internal Transitions
- •Trigger
- •Stereotypes
- •Bibliography
- •Index
![](/html/75672/2303/html_3iAzqyDIia.Sav3/htmlconvd-3gbB7Q411x1.jpg)
Chapter 9 Design Patterns and Idioms
CLASS HIERARCHY VS TYPE ERASURE IDIOM
As we know from the section about object-orientation in Chapter 6, polymorphism in general means providing a single interface to entities of different types. Many object-oriented design patterns, including most of those in the Design Patterns book, rely on class hierarchies with dynamic (runtime) polymorphism realized by virtual member function overrides.
You may also remember the section on type erasure techniques in Chapter 6. There, I presented an alternative way to realize dynamic polymorphism that did not rely on a class hierarchy: the Type Erasure idiom. At this point I would like just to remind you that Strategy, as well as other design patterns, could also be implemented with the help of this idiom.
Command
Software systems usually have to perform a variety of actions due to the reception of instructions. Users of text processing software, for example, issue a variety of commands by interacting with the software’s user interface. They want to open a document, save a document, print a document, copy a piece of text, paste a copied piece of text, etc. This general pattern is also observable in other domains. For example, in the financial world, there could be orders from a customer to his securities dealer to buy shares, sell shares, etc. And in a more technical domain like manufacturing, commands are used to control industrial facilities and machines.
When implementing software systems that are controlled by commands, it is important to ensure that the request for an action is separated from the object that actually performs the action. The guiding principle behind this is loose coupling (see Chapter 3) and separation of concerns.
A good analogy is a restaurant. In a restaurant, the waiter accepts the customer’s order, but she is not responsible for cooking the food. That is a task for the restaurant’s kitchen. Actually, it is even transparent for the customer how the food is prepared. Maybe someone at the restaurant prepares the food, but the food might also be delivered from somewhere else.
In object-oriented software development, there is a behavioral pattern named Command (Action) that fosters this kind of decoupling. Its mission statement is as follows:
402
Chapter 9 Design Patterns and Idioms
“Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.”
—Erich Gamma et al., Design Patterns [Gamma95]
A good example of the Command pattern is a client/server architecture, where a client— the so-called invoker—sends commands that should be executed on a server, which is referred to as the receiver.
Let’s start with the abstract Command, which is a simple and small interface shown in Listing 9-17.
Listing 9-17. The Command interface
#include <memory>
class Command { public:
virtual ~Command() = default; virtual void execute() = 0;
};
using CommandPtr = std::shared_ptr<Command>;
We’ve also introduced a type alias (CommandPtr) for a smart pointer to commands. This abstract Command interface can now be implemented by various concrete
commands. Let’s first take a look at a very simple command, the output of the string "Hello World!". See Listing 9-18.
Listing 9-18. A First and Very Simple Implementation of a Concrete Command
#include <iostream>
class HelloWorldOutputCommand : public Command { public:
void execute() override {
std::cout << "Hello World!" << "\n";
}
};
403
Chapter 9 Design Patterns and Idioms
Next, we need the element that accepts and executes the commands. This element is called Receiver in the general description of this design pattern. In our case, it is a class named Server that plays this role. See Listing 9-19.
Listing 9-19. The Receiver Command
#include "Command.h"
class Server { public:
void acceptCommand(const CommandPtr& command) { command->execute();
}
};
Currently, this class contains only one simple public member function that can accept and execute commands.
Finally, we need the so-called invoker, which is the Client class in our client/server architecture. See Listing 9-20.
Listing 9-20. The Client Sends Commands to the Server
class Client { public:
void run() {
Server theServer { };
CommandPtr helloWorldOutputCommand = std::make_shared<HelloWorldOutput Command>();
theServer.acceptCommand(helloWorldOutputCommand);
}
};
Inside the main() function, we find the simple code shown in Listing 9-21.
Listing 9-21. The main() Function
#include "Client.h"
int main() {
Client client { };
404
Chapter 9 Design Patterns and Idioms
client.run(); return 0;
}
If this program is now being compiled and executed, the "Hello World!" output will appear on stdout. Well, at first sight, this may seem not very exciting, but what we have achieved through the Command pattern is that the origination and sending off of the command is decoupled from its execution. We can now handle command objects as well as other objects.
Since this design pattern supports the open-closed principle (OCP; see Chapter 6) very well, it is also very easy to add new commands with negligible minor modifications of existing code. For instance, if we want to force the Server to wait for a certain amount of time, we can just add the new command shown in Listing 9-22.
Listing 9-22. Another Concrete Command That Instructs the Server to Wait
#include "Command.h"
#include <chrono> #include <thread>
class WaitCommand : public Command { public:
explicit WaitCommand(const unsigned int durationInMilliseconds) noexcept : durationInMilliseconds{durationInMilliseconds} { };
void execute() override {
std::chrono::milliseconds timespan(durationInMilliseconds); std::this_thread::sleep_for(timespan);
}
private:
unsigned int durationInMilliseconds { 1000 };
};
Now we can use the new WaitCommand, as shown in Listing 9-23.
405
![](/html/75672/2303/html_3iAzqyDIia.Sav3/htmlconvd-3gbB7Q415x1.jpg)
Chapter 9 Design Patterns and Idioms
Listing 9-23. The New WaitCommand in Use
class Client { public:
void run() {
Server theServer { };
const unsigned int SERVER_DELAY_TIMESPAN { 3000 };
CommandPtr waitCommand = std::make_shared<WaitCommand>(SERVER_DELAY_ TIMESPAN);
theServer.acceptCommand(waitCommand);
CommandPtr helloWorldOutputCommand = std::make_shared<HelloWorldOutputC ommand>();
theServer.acceptCommand(helloWorldOutputCommand);
}
};
Figure 9-8 shows an overview of the structure that has been originated so far in the form of an UML class diagram.
Figure 9-8. The server knows the Command interface, but not any concrete command
As can be seen in this example, we can parameterize commands with values. Since the signature of the pure virtual execute() member function is specified as
parameterless by the Command interface, the parameterization is done with the help of an
406