Chapter 7 OOP Analysis and Design
Chapter 7 OOP Analysis and Design
and
External Polymorphism
Design Patterns
Exploring Three Key Design Patterns
• Build Bridges to Remove Physical
Dependencies
ElectricCar.h
ElectricCar.cpp
A slightly better approach:
Only store a pointer to ElectricEngine
ElectricEngine.h
• The physical dependency is gone.
ElectricCar.h ElectricCar.cpp
Using Abstract Class
ElectricEngine.h Engine.h
ElectricCar.h ElectricCar.cpp
“Decouple an abstraction from its implementation so that the two
can vary independently.”
In our example,
ElectricCar.cpp ElectricCar.cpp
The Pimpl Idiom
Pointer to Implementation
• Used in C and C++
• Make Bridge design pattern simpler to implement
• Increase ABI stability
ABI(Application Binary Interface)
Stability
• Binary interface of the language is guaranteed to remain stable across different versions of the
compiler and platforms
• Libraries, frameworks, and executables compiled with version A of the compiler can be used with
version B of the compiler.
• You no longer need to make sure that the C++ version of the library or framework your project uses
matches the project's C++ version.
Before Pimpl
• Person class needs to be
extended or changed over time
• Whenever Person changes, the
users of Person have to
recompile their code.
• To hide all changes to the
implementation details of Person,
you can use the Bridge design
pattern.
Implementing Pimpl 1
• No need to provide an abstraction as
a base class
• Just introduce a private, nested class
called Impl (12). Note: Impl is only
declared not defined.
• Only data member remaining in the
Person class is the std::unique_ptr to
an Impl instance (13)
• All other data members, and non-
virtual helper functions, will moved
into the Impl class (14)
• All implementation details are hidden
from the users of Person
Implementing Pimpl 2
• Pimpl idiom is bridge design pattern in
its simplest form: local, nonpolymorphic
• The Person constructor initializes the
pimpl_ data member by
std::make_unique() (15).
• Normally, make_unique automatically
deals with allocated dynamic memory,
• But compiler searches destructor of Impl
in Person.h file.
• Solution is define a destructor in header
and declare it in class via =default. (16)
Implementing Pimpl 3
• std::unique_ptr cannot be copied, it's designed to have only one owner
• So we must implement copy constructor(17) and copy assingment
operator(18)
• year_of_
• birth(21)
Strategy Bridge
Reduction of Physical
Primary Focus Reduction of Logical dependencies
dependencies
Strategy Example
•The actual type
of
DatabaseEngine
is passed in
from the outside
(22)
Strategy Dependency Graph
• Database class is on the same architectural level as the DatabaseEngine abstraction
• DatabaseEngine implements the behaviour
• Database is depending only on the abstraction(DatabaseEngine), not on implementation
Bridge Example
• Instead of accepting an engine from outside, the
constructor sets it internally
Bridge Dependency Graph
• Database class is on the same architectural level as the ConcreteDatabaseEngine class
• Bridge physically decoupled via abstraction but logical coupling to implementation remains
Shortcomings of Bridge
• Bridge design pattern reduces physical dependencies but adds
performance overhead.
private:
ShapeT shape_;
DrawStrategy drawer_;
};
Std::function Alternative: Default Template
struct DefaultDrawer
{
template <typename T>
void operator()(T const &obj) const
{
draw(obj);
}
};
template <typename ShapeT, typename DrawStrategy = DefaultDrawer>
class ShapeModel : public ShapeConcept
{
public:
explicit ShapeModel(ShapeT shape, DrawStrategy drawer = DefaultDrawer{})
// ... as before
};
STD::FUNCTION VS DEFAULT TEMPLATE
PARAMETER IMPLEMENTATION
• Fewer runtime indirection.
• Not artificially augment shape classes with a template argument to
configure the drawing behavior.
• Not force additional code into a header file by turning a regular class
into a class template.
#include <Circle> OpenGLDrawStrategy.h
#include <Square>
#include /* OpenGL graphics library headers */
class OpenGLDrawStrategy
{
public:
explicit OpenGLDrawStrategy(/* Drawing related arguments */);
void operator()(Circle const &circle) const;
void operator()(Square const &square) const;
private:
/* Drawing related data members, e.g. colors, textures, ... */
};
#include <Circle.h>
#include <Square.h>
#include <Shape.h>
#include <OpenGLDrawStrategy.h>
#include <memory>
#include <vector>
int main()
{
using Shapes = std::vector<std::unique_ptr<ShapeConcept>>;
using CircleModel = ShapeModel<Circle, OpenGLDrawStrategy>;
using SquareModel = ShapeModel<Square, OpenGLDrawStrategy>;
Shapes shapes{};
// Creating some shapes, each one
// equipped with an OpenGL drawing strategy
• Disadvantage:
ØThere is only one major disadvantage, though:
vThe External Polymorphism design pattern does not really fulfil the expectations of a
clean and simple solution, and definitely not the expectations of a value semantics–based
solution.
• It does not help to reduce pointers,
• It does not reduce the number of manual allocations,
• It does not lower the number of inheritance hierarchies,
• It does not help to simplify user code.