- •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
Chapter 9 Design Patterns and Idioms
initialization constructor. Furthermore, we didn’t have to change anything in the Server class, because it was able to treat and execute the new command immediately.
The Command pattern provides manifold possibilities of applications. For example, commands can be queued. This also supports an asynchronous execution of the commands: The invoker sends the command and can then do other things immediately, but the command is executed by the receiver at a later time.
However, something is missing! In the quoted mission statement of the Command pattern, you can read something about “…support undoable operations.” Well, the following section is dedicated to that topic.
Command Processor
In our small example of a client/server architecture from the previous section, I cheated a bit. In reality, a server would not execute the commands in the manner I demonstrated. The command objects that are arriving at the server would be distributed to the internal parts of the server that are responsible for the execution of the command. This can, for example, be done with the help of another pattern that is called Chain of Responsibility (this pattern is not described in this book).
Let’s consider another, more complex example. Assume that we have a drawing program. Users of this program can draw many different shapes, for instance, circles and rectangles. For this purpose, corresponding menus are available in the program’s user interface via that these drawing operations can be invoked. I’m pretty sure that you’ve guessed it: the well-skilled software developers of this program implemented the Command pattern to perform these drawing operations. A stakeholder requirement, however, states that a user of the program can also undo drawing operations.
To fulfill this requirement, we need, first of all, undoable commands. See Listing 9-24.
Listing 9-24. The UndoableCommand Interface Is Created by Combining Command and Revertable
#include <memory>
class Command { public:
virtual ~Command() = default; virtual void execute() = 0;
};
407
Chapter 9 Design Patterns and Idioms
class Revertable { public:
virtual ~Revertable() = default; virtual void undo() = 0;
};
class UndoableCommand : public Command, public Revertable { };
using CommandPtr = std::shared_ptr<UndoableCommand>;
According to the interface segregation principle (ISP; see Chapter 6), we’ve added another interface called Revertable that supports the Undo functionality. This new interface can be combined with the existing Command interface using inheritance to an
UndoableCommand.
As an example of many, different undoable drawing commands, I just show the concrete command for the circle in Listing 9-25.
Listing 9-25. An Undoable Command for Drawing Circles
#include "Command.h" #include "DrawingProcessor.h" #include "Point.h"
class DrawCircleCommand : public UndoableCommand { public:
DrawCircleCommand() = delete;
DrawCircleCommand(DrawingProcessor& receiver, const Point& centerPoint, const double radius) noexcept :
receiver { receiver }, centerPoint { centerPoint }, radius { radius } {
}
void execute() override { receiver.drawCircle(centerPoint, radius);
}
void undo() override { receiver.eraseCircle(centerPoint, radius);
}
408
Chapter 9 Design Patterns and Idioms
private:
DrawingProcessor& receiver; const Point centerPoint; const double radius;
};
It is easy to imagine that the commands for drawing a rectangle and other shapes look very similar. The executing receiver of the command is a class named DrawingProcessor, which is the element that performs the drawing operations. A reference to this object
is passed along with other arguments during the construction of the command (see initialization constructor). At this place I show only a small excerpt of the presumably complex class DrawingProcessor, because it does not play an important role for the understanding of the pattern. See Listing 9-26.
Listing 9-26. The DrawingProcessor Is the Element that Will Perform the Drawing Operations
class DrawingProcessor { public:
void drawCircle(const Point& centerPoint, const double radius) { // Instructions to draw a circle on the screen...
};
void eraseCircle(const Point& centerPoint, const double radius) { // Instructions to erase a circle from the screen...
};
// ...
};
Now we come to the centerpiece of this pattern, the CommandProcessor; see Listing 9-27.
Listing 9-27. The CommandProcessor Class Manages a Stack of Undoable Command Objects
#include <stack>
class CommandProcessor { public:
409
Chapter 9 Design Patterns and Idioms
void execute(const CommandPtr& command) { command->execute(); commandHistory.push(command);
}
void undoLastCommand() {
if (commandHistory.empty()) { return;
}
commandHistory.top()->undo(); commandHistory.pop();
}
private:
std::stack<std::shared_ptr<Revertable>> commandHistory;
};
The CommandProcessor class (which is by the way not thread-safe when using the above implementation) contains a std::stack<T> (defined in the <stack> header), which is an abstract data type that operates as a LIFO (Last-In First-Out). After an execution of a command has been triggered by the CommandProcessor::execute() member function, the command object is stored on the commandHistory stack. When calling the CommandProcessor::undoLastCommand() member function, the last command stored on the stack is undone and then removed from the top of the stack.
The undo operation can now be modeled as a command object. In this case, the command receiver is, of course, the CommandProcessor itself. See Listing 9-28.
Listing 9-28. The UndoCommand Prompts the CommandProcessor to Perform an Undo
#include "Command.h" #include "CommandProcessor.h"
class UndoCommand : public UndoableCommand { public:
explicit UndoCommand(CommandProcessor& receiver) noexcept : receiver { receiver } { }
410
Chapter 9 Design Patterns and Idioms
void execute() override { receiver.undoLastCommand();
}
void undo() override {
// Intentionally left blank, because an undo should not be undone.
}
private:
CommandProcessor& receiver;
};
Lost the overview? It’s once again time for a “big picture” in the form of a UML class diagram, as shown in Figure 9-9.
Figure 9-9. The CommandProcessor (on the right) executes the commands it receives and manages a command history
411