Programming Policies
Picture this situation: you've got a continuous flow of data coming in, and your task is to process it and present the results in a neat format. You quickly realize that the smartest way to handle this is by separation of concerns. You create one class specifically for processing the data and another for formatting it. Then, you compose your Stream class with both of these classes within.
Now, let's step it up a notch. Picture having various streams of data, each with its own unique processing and formatting needs. On top of that, some of these streams can be interrupted during processing.
When you try applying the solution used in the first scenario to the second one, you'll quickly notice that there's a significant amount of shared behavior among your streams. You might begin by inheriting from a base Processor class and a Formatter, and then using a reference or pointer to specific classes in your implementation. However, this approach lacks control over interfaces. This means that regardless of whether your stream can be interrupted or not, you'll end up with functions in your interface that mention interruption capabilities.
Moreover, inheritance represents the greatest form of coupling. Despite initially using composition to minimize coupling, resorting to inheritance can ultimately lead to a more complicated situation.
So now let's look at the design pattern in C++ addressing this situation called Policy. The Idea of this pattern is taken from a book called Hands-On Design Patterns with C++ by Fedor G. Pikus. [https://www.packtpub.com/product/hands-on-design-patterns-with-c/9781788832564 ]
We begin with a simple step. Our stream class requires a formatter, but we leave the decision on the type of formatter to be made during construction. This is a straightforward use case of templates.
Here, you can choose from various formatters. For instance, a basic formatter for writing into files or an advanced formatter for plotting data on a map. The key point is that the decision is left to the user of our classes. This represents the Policy Design Pattern in its simplest form.
In this form, our formatter will be quite limited. It would be beneficial if we could grant it full access to our Stream class. One way to achieve this is through CRTP (Curiously Recurring Template Pattern) [https://en.cppreference.com/w/cpp/language/crtp ]. Although the syntax may be a bit convoluted, our formatters will gain access to the exact type of our streams.
Sometimes, our policies only involve behavior—a set of functions that we want to operate on our underlying data. For example, for processing the stream, we can define another policy and create a class member based on that type, similar to what we did with the formatter. However, this approach may lead to memory waste. Instead, by inheriting from a policy, we can incorporate all the functionality while also benefiting from the Empty Base Optimization [https://en.cppreference.com/w/cpp/language/ebo ].
If you recall, some of our streams should be capable of handling interruptions. Fortunately, using Policies, we have a way to modify the public interface of our Stream classes.
领英推荐
Here I have used C++20 Concepts to enable and disable the interrupt method in Stream class.
For InterruptManagers that cannot handle interrupts, our Stream class won't have an interrupt method. For others, it will call the policy's interrupt method.
The list of policies can grow rapidly, and sometimes you want to default to all policies except the last one. In such situations, you must assign all the types to reach the last one. Other times, you may want a Stream class similar to the one you already have but with a different formatter. There is a way to address these issues and make life easier for our users, using type alias declarations [https://en.cppreference.com/w/cpp/language/type_alias ].
With the help of these aliases, we can take one stream type and replace the formatter with a new one in it. Alternatively, we can chain them together to come up with a elegant expression like the following:
This is the full picture:
The source code is available on my GitHub repository [https://github.com/MhmRhm/cpplab/tree/main/policy ].
I welcome your thoughts and feedback on this design pattern and the accompanying sample code.
EV-Infrastructure Software Architect @ MAPNA (MECO)
7 个月I feel a little complication though