0% found this document useful (0 votes)
68 views60 pages

Chapter 6

The document discusses three design patterns: Adapter, Observer, and CRTP. The Adapter pattern facilitates collaboration between incompatible interfaces, while the Observer pattern allows for decoupled notifications of state changes. CRTP introduces static type categories for related types, but it has limitations, such as the lack of a common base class and the requirement for derived classes to be templates.

Uploaded by

yesoj68482
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
68 views60 pages

Chapter 6

The document discusses three design patterns: Adapter, Observer, and CRTP. The Adapter pattern facilitates collaboration between incompatible interfaces, while the Observer pattern allows for decoupled notifications of state changes. CRTP introduces static type categories for related types, but it has limitations, such as the lack of a common base class and the requirement for derived classes to be templates.

Uploaded by

yesoj68482
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 60

CHAPTER 6

ADAPTER DESIGN PATTERN


OBSERVER DESIGN PATTERN
CRTP DESIGN PATTERN
THE ADAPTER DESIGN
PATTERN
Contents:
Menus
All relevant data of several restaurants
THE ADAPTER DESIGN
PATTERN
Contents:
Menus
All relevant data of several restaurants

Downloads and collects data from


multiple sources in XML format
THE ADAPTER DESIGN
PATTERN
Contents:
Menus
All relevant data of several restaurants

Downloads and collects data from


The third party library
multiple sources in XML format
What we need:
An adapter
This adapter takes all of our XML data and transforms it to JSON making it
compatible with the 3rd party library we are making use of
We have talked about what is called object adapter that refers to the fact that you
store an instance of the wrapped type.
There is also class adapter.

Object adapter is more


flexible than class adapter.

Then why you use class


adapter?
If you have to override a
virtual function.
If you need access to a
protected member
function.
There is also function adapters.

The purpose of the free begin( ) and end( ) functions is to adapt the iterator interface
of any type to the expected STL iterator interface.
Strategy versus Adapter

Intent is different.

Primary focus of an Adapter is to standardize interfaces and integrate incompatible


functionality into an existing set of conventions.

Primary focus of the Strategy design pattern is to enable the configuration of


behavior from the outside, building on and providing an expected interface.
Adapter design pattern:
Allows objects with incompatible interfaces to collaborate with one another.
Creates a middle-layer class that serves as a translator.
Applies the single responsibility and open-closed principles:
You can seperate data conversion code from the primary busines logic of the
program.
You can introduce new types of adapters without breaking existing code.

What about cons?


The overall complexity of the code increases because you need to introduce a set of
new interfaces and classes. Sometimes it’s simpler just to change the service class
so that it matches the rest of your code.
Despite the value of the Adapter design pattern, there is one issue with this design pattern
To make things worse, for some reason ducks and turkeys are expected be used
together. One possible way to make this work is to pretend that a turkey is a
duck. After all, a turkey is pretty similar to a duck.
While this is an amusing interpretation of duck typing, this example nicely
demonstrates that it’s way too easy to integrate something alien into an existing
hierarchy. A Turkey is simply not a Duck, even if we want it to be. I would argue
that likely both the quack() and the fly() function violate the LSP
In summary, the Adapter design pattern can be considered one of the
most valuable design patterns for combining different pieces of
functionality and making them work together. Still, do not abuse the
power of Adapter in some heroic effort to combine apples and oranges
(or even oranges and grapefruits: they are similar but not the same).
Always be aware of LSP expectations.
Guideline 25: Apply
Observers as an
Abstract Notification
Mechanism
The Observer Design Pattern Explained

In many software situations it’s desirable to get feedback as soon as some state
change occurs: a new job is added to a task queue, a setting is changed in some
configuration object, a result is ready to be picked up, etc. But at the same time, it
would be highly undesirable to introduce explicit dependencies between the
subject (the observed entity that changes) and its observers (the callbacks that are
notified based on a state change). On the contrary, the subject should be oblivious
to the potentially many different kinds of observers. And that’s for the simple
reason that any direct dependency would make the software harder to change
and harder to extend. This decoupling between the subject and its potentially
many observers is the intent of the Observer design pattern.
A Classic Observer Implementation

The central element is the Observer base class:

The most important implementation detail of this class is the pure virtual
update() function ( ), which is called whenever the observer is notified of some
state change. There are three alternatives for how to define the update() function,
which provide a reasonable implementation and design flexibility. The first
alternative is to push the updated state via one or even several update() functions:
This form of observer is commonly called a push observer. In this form, the
observer is given all necessary information by the subject and therefore is not
required to pull any information from the subject on its own. This can reduce
the coupling to the subject significantly and create the opportunity to reuse the
Observer class for several subjects.

But isn’t this a violation of the ISP?


We could separate an Observer with several update() functions into smaller
Observer classes:
There are several more potential downsides of a push observer.
First, the observers are always given all the information, whether they need
it or not. Thus, this push style works well only if the observers need the
information most of the time. Otherwise, a lot of effort is lost on unnecessary
notifications.
Second, pushing creates a dependency on the number and kind of arguments
that are passed to the observer. Any change to these arguments requires a lot
of subsequent changes in the deriving observer classes.
Some of these downsides are
resolved by the second Observer
alternative. It’s possible to only
pass a reference to the subject to
the observer:

Due to the lack of specific information passed to the observer, the classes
deriving from the Observer base class are required to pull the new
information from the subject on their own. For this reason, this form of
observer is commonly called a pull observer.
The advantage is the reduced dependency on the number and kinds of
arguments. Deriving observers are free to query for any information, not just
the changed state
There is a third alternative, which removes a lot of the previous disadvantages
and thus becomes our approach of choice: in addition to passing a reference to
the subject, we pass a tag to provide information about which property of a
subject has changed:

The tag may help an


observer to decide on its own
whether some state change
is interesting or not.
This, unfortunately,
increases the coupling of the
Observer class to a specific
subject.
So, how do we remove the dependency on a specific Subject?

Class Templates!
We can create a Person class that represents an observed Person.
We can implement Person:attach and Person:detach as following.

These implementation will handle multiple registration and deregistration of


objects that is not registered.
Implementation of Person:notify

Range-based for loop would be enough,


but this implementation has its own benefits.
The notify() function is called in all
three setter functions.

We pass a different tag for every


function to indicate which property
has changed. With this way,
observers can react depending on
the changed property.
Now, we can implement fully OCP-conforming observers.
NameObserver and AddressObserver
We can look at this example
of Person and Observer in
usage.

Equipped with two


observers, we are now
notified whenever either
the name or address of a
person changes.
We can observe the levels
and architectural
boundaries between the
classes that we have
implemented with this
dependency graph.
An Observer
Implementation Based on
Value Semantics

We can implement the


Observer DP using value
semantics too.

This approach separate


concerns by separating
update function from the
class.
A main() function
example which uses
value semantics

The flexibility gained with


std::function is easily
demonstrated at the example

Now, the choice of how to


implement the update()
function is completely up to
the observer’s implementer.
Guideline 26: Use
CRTP to Introduce
Static Type
Categories
Here the implementation of Dynamic Vector class for Linear Algebra
Library.
Here the implementation of Static Vector class for Linear Algebra
Library.
These two classes are almost identical. The StaticVector class provides the
same interface as the DynamicVector, such as the nested types value_type,
iterator, and const_iterator; the size member function; the subscript operators;
and the begin and end functions.

The important difference between these classes is performance. The


performance difference is caused by that Static Vector class does not allocate its
elements dynamically.

The problem of this implementation is that one of the software principles,


DON’T REPEAT YOURSELF is violated.
First possible solution for this problem;

Creating a template
function for output
operator.

It violates SPECIFY CONCEPTS FOR ALL TEMPLATE ARGUMENTS concept. This


function template will in fact accept any type: DynamicVector, StaticVector,
std::vector, std::string, and fundamental types such as int and double.
Second possible solution for this problem;
Creating a template base class
for Static and Dynamic
Vectors.

Because of the virtual functions in base class DenseVector, this approach for
solving the problem decreases the perforamnce.
The CRTP Design Pattern Explained
Intent: “Define a compile-time abstraction for a family of related types.”

The compile-time relationship between the DenseVector base class and the DynamicVector derived class is created by
upgrading the base class to a class template:
The curious detail about CRTP is that the new template parameter of the DenseVector base class
represents the type of the associated derived class

Derived classes, for instance, the DynamicVector, are expected to provide their own type to
instantiate the base class

Is that even possible?


The answer is yes. To instantiate a template, you do not need the complete definition of a
type. It is sufficient to use an incomplete type. Such an incomplete type is available after the
compiler has seen the class DynamicVector declaration. Therefore, the DynamicVector class
can indeed use itself as a template argument to the DenseVector base class.
Naming the template parameter of the base class
As discussed in “Guideline 14: Use a Design Pattern’s Name to Communicate Intent”, it helps to
communicate intent by using the name of the design pattern or names commonly used for a pattern.
For that reason, we could name the parameter CRTP, which nicely communicates the pattern but
unfortunately only to the initiated. Everyone else will be puzzled by the acronym. Therefore, the
template parameter is often called Derived, which perfectly expresses its purpose and communicates
its intent: it represents the type of the derived class.
The base class is now aware of the actual type of the derived type. While it
still represents an abstraction and the common interface for all dense
vectors, it is now able to access and call the concrete implementation in the
derived type.
For instance, in the size() member function (17): the DenseVector uses a
static_cast to convert itself into a reference to the derived class and calls the
size() function on that. What at first glance may look like a recursive
function call (calling the size() function within the size() function) is in fact a
call of the size() member function in the derived class (18).

“So this is the compile-time relationship we were talking about.


The base class represents an abstraction from concrete-derived
types and implementation details but still knows exactly where
the implementation details are. So we really do not need any
virtual function.” Correct.

With CRTP, we are now able to implement a common interface and forward every call to the derived class
by simply performing a static_cast. And there is no performance penalty for doing this. In fact, the base
class function is very likely to be inlined, and if the DenseVector is the only or first base class, the static_cast
will not even result in a single assembly instruction. It merely tells the compiler to treat the object as an
object of the derived type.
To provide a clean CRTP base class, Let’s update a couple of details,
though:

Since we want to avoid any virtual functions, we’re also not interested in
a virtual destructor. Therefore, we implement the destructor as a
nonvirtual function in the protected section of the class (19). This
perfectly adheres to Core Guideline C.35: A base class destructor should
be either public and virtual, or protected and non-virtual.

We should also avoid using a static_cast in every single member


function of the base class. Although it would be correct, any cast
should be considered suspicious, and casts should be minimized. For
that reason, we add the two derived() member functions, which
perform the cast and can be used in the other member functions (20).
This resulting code not only looks cleaner and adheres to the DRY
principle, but it also looks far less suspicious.
We can now go ahead and define the subscript operators and the begin() and end() functions:

These functions are not as straightforward as the size()


member function. In particular, the return types prove to be a
little harder to specify, as these types depend on the
implementation of the Derived class. This is why the derived
types provide a couple of nested types, such as value_type,
iterator, and const_iterator
We query for the value_type, iterator, and const_iterator types in the derived class and use these to specify our return types (21).

Is it that easy to solve?


If you compile the code you will get the following error

The reason for the error is that there is no 'value_type' in the


DynamicVector class. Not yet! When you query for the nested types,
the definition of the DynamicVector class hasn’t been seen, and
DynamicVector is still an incomplete type.

“But why can we call the member functions of the derived class? Shouldn’t
this result in the same problem?”
Luckily, this works (otherwise the CRTP pattern would not work at all). But it only works because
of a special property of class templates: member functions are only instantiated on demand,
meaning when they are actually called. Since an actual call usually happens only after the definition
of the derived class is available, there is no problem with a missing definition. At that point, the
derived class is not an incomplete type anymore.
Let’s see how we solve this and specify the return types of the subscript operators and begin()
and end() functions...
The most convenient way to handle this is to use return type deduction. This is a perfect opportunity to use the
decltype(auto) return type:
Analyzing the Shortcomings of the CRTP Design Pattern

CRTP pattern works perfectly so far, apart from these slightly-more-complex-than-


usual implementation details, isn’t this the solution to all performance issues with
virtual functions? And isn’t this the key, the holy grail for all inheritance-related
problems?

At first sight, CRTP most definitely looks like the ultimate solution for all kinds of
inheritance hierarchies. Unfortunately, that is an illusion. Remember: every design pattern
comes with benefits but unfortunately also with drawbacks. And there are several pretty
limiting drawbacks to the CRTP design pattern. Let’s discuss them one by one ...
Analyzing the Shortcomings of the CRTP Design Pattern
The first, and one of the most restricting, drawbacks is the lack of a common
base class.
There is no common base class! Effectively, every single derived class has a different base
class.
For example,
The DynamicVector<T> class has the DenseVector<DynamicVector<T>>
base class.
The StaticVector<T, Size> class has the DenseVector <StaticVector<T,
Size>> base class

Thus, whenever a common base class is required, a common abstraction that can
be used, for instance, to store different types in a collection, the CRTP design
pattern is not the right choice

Couldn’t we just make the CRTP base class derive from a common base
class ?
No, not really, because this would require us to introduce virtual
functions again...
Analyzing the Shortcomings of the CRTP Design Pattern
The second, also potentially very limiting drawback is that everything that comes in touch with a
CRTP base class becomes a template itself.
That is particularly true for all functions that work with such a base class. Consider, for instance, the
upgraded output operator and the l2norm() function:

These two functions should work with all classes deriving from the DenseVector CRTP class. And of course, they
should not depend on the concrete types of the derived classes. Therefore, these two functions must be function
templates: the Derived type must be deduced. It might be highly undesirable to turn lots of code into templates and
move the definitions into header files, effectively sacrificing the encapsulation of source files.
Analyzing the Shortcomings of the CRTP Design Pattern

Third, CRTP is an intrusive design pattern


Deriving classes have to explicitly option in by inheriting from the CRTP base class. While this may be a
nonissue in our own code, you cannot easily add a base class to foreign code.

In such a situation, you would have to resort to the Adapter design pattern (see “Guideline 24: Use
Adapters to Standardize Interfaces”). Thus, CRTP does not provide the flexibility of nonintrusive design
patterns (e.g., the Visitor design pattern implemented with std::variant, the Adapter design pattern, and so on).

Last but not least, CRTP does not provide runtime polymorphism, only compile-time polymorphism.
Therefore, the pattern makes sense only if some kind of static-type abstraction is required. If not, it
is again not a replacement for all inheritance hierarchies.
USING C++20 CONCEPTS INSTEAD OF CTRP
C++20 concepts allow you to specify constraints on template parameters, making
it easier to express and enforce the requirements for template arguments.
While this formulation of the
output operator effectively
constrains the function template
to only those types that provide
the expected interface, it does
not completely restrict the
function template to our set of
dense vector types. It’s still
possible to pass std::vector and
std::string.
To constrain std::vector and std::string, tag class is used. A tag class is a design
pattern in object-oriented programming where a class is used to carry
information.

In this way, the


constration problem
is solved but the
solution is no longer
nonintrusive.

So, if there is a class which is already derived from another class for different
purpose, it cannot be a part of the DenseVector concept easily.
To make the solution intrusive, compile-time indirection by a customizable type
trait class is used.
In this way, instead of directly
querying a given type, the
DenseVector concept would ask
indirectly via the IsDenseVector type
trait. So, classes can intrusively derive
from the DenseVectorTag or to
nonintrusively specialize the
IsDenseVector type trait.
Overview of CTRP vs C++20 Concepts
CRTP comes with a couple of potentially limiting drawbacks, such as the lack
of a common base class, the quick spreading of template code, and the restriction
to compile-time polymorphism. With C++20, consider replacing CRTP with
concepts, which provide an easier and nonintrusive alternative
Guideline 27: Use
CRTP for Static
Mixin Classes
StrongType class template, which represents a wrapper around any other type for
the purpose of creating a unique, named type

This class can, for instance, be used to define the types Meter, Kilometer, and
Surname
they represent distinct types (strong types) with semantic meaning that cannot be
(accidentally) combined in arithmetic operations, for example, addition
Whereas due to the formulation of this addition operator it is not possible to add two
different instantiations of StrongType together (e.g., Meter and Kilometer), it would
enable the addition of two instances of the same instantiation of StrongType.
These mixin classes are implemented in terms of
the CRTP. Consider, for instance, the Addable
class template, which represents the addition
operation:
Our solution of choice is to provide the mixins
in the form of optional template arguments.
For that purpose, we extend the StrongType
class template by a pack of variadic template
template parameters

This extension enables us to individually


specify, for each single strong type, which
skills are desired. Consider, for instance, the
two additional skills Printable and
Swappable:
Both Meter and Kilometer can be added, printed, and swapped , while Surname is
printable and swappable, but not addable.
Bizi dinlediğiniz için
Teşekkür ederiz!

You might also like