Professional Documents
Culture Documents
Principles
Klaus Iglberger, Core C++, May 2020
klaus.iglberger@gmx.de
C++ Trainer since 2016
Email: klaus.iglberger@gmx.de
Klaus Iglberger
2
Software Design
3
Software Design
4
Software Design
5
Software Design
Architectural Boundaries
6
Software Design
7
The SOLID Principles
Single-Responsibility Principle
Open-Closed Principle
8
The SOLID Principles
Single-Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
10
The SOLID Principles
11
The Single-Responsibility Principle (SRP)
12
The Single-Responsibility Principle (SRP)
13
The Single-Responsibility Principle (SRP)
14
The Single-Responsibility Principle (SRP)
15
The Single-Responsibility Principle (SRP)
16
The Single-Responsibility Principle (SRP)
17
The Single-Responsibility Principle (SRP)
class Circle
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
private:
double radius;
// ... Remaining data members
};
18
The Single-Responsibility Principle (SRP)
class Circle
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
private:
double radius;
// ... Remaining data members
};
19
The Single-Responsibility Principle (SRP)
class Circle
{
public:
// ...
void draw( Screen& s, /*...*/ );
void draw( Printer& p, /*...*/ );
void serialize( ByteStream& bs, /*...*/ );
// ...
};
A Circle changes if …
… the basic properties of a circle change;
… the Screen changes;
… the Printer changes;
… the ByteStream changes;
… the implementation details of draw() change;
… the implementation details of serialize() change;
…
20
The Single-Responsibility Principle (SRP)
Overlap
Circle Square
21
The Single-Responsibility Principle (SRP)
Overlap Drawing
22
The Single-Responsibility Principle (SRP)
namespace std {
return dest;
}
} // namespace std
23
The Single-Responsibility Principle (SRP)
24
The Open-Closed Principle (OCP)
25
The Open-Closed Principle (OCP)
26
OCP: A Procedural Approach
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
private:
ShapeType type;
};
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
private:
ShapeType type;
};
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
, type{ t }
{}
private:
ShapeType type;
};
private:
ShapeType type;
};
private:
double radius;
// ... Remaining data members
};
private:
double radius;
private:
double side;
// ... Remaining data members
};
private:
double side;
// ... Remaining data members
};
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
33
OCP: A Procedural Approach
enum ShapeType
{
circle,
square,
rectangle
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
private:
ShapeType type;
};
private:
ShapeType type;
};
private:
double radius;
// ... Remaining data members
};
private:
double radius;
private:
double side;
// ... Remaining data members
};
private:
double side;
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
private:
double radius;
// ... Remaining data members
};
private:
double radius;
// ... Remaining data members
};
private:
double side;
// ... Remaining data members
};
private:
double side;
// ... Remaining data members
};
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
private:
double side;
OCP: An Object-Oriented Approach
};
// ... Remaining data members
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
43
class Shape
{
public:
private:
double radius;
// ... Remaining data members
};
class Circle
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
private:
double radius;
// ... Remaining data members
};
class Square
{
public:
explicit Square( double s )
: side{ s }
, // ... Remaining data members
{} 45
OCP: A Type-Erased Approach
class Circle
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
private:
double radius;
// ... Remaining data members
};
class Square
{
public:
explicit Square( double s )
: side{ s }
, // ... Remaining data members
{} 46
// ... getCenter(), getRotation(), ...
private:
double radius;
class Square
{
public:
explicit Square( double s )
: side{ s }
, // ... Remaining data members
{}
private:
double side;
// ... Remaining data members
};
class Shape
{
private:
struct Concept
{ 47
virtual ~Concept() {}
// ... getCenter(), getRotation(), ...
private:
double side;
OCP: A Type-Erased Approach
};
// ... Remaining data members
class Shape
{
private:
struct Concept
{
virtual ~Concept() {}
virtual void do_translate( Vector3D const& v ) const = 0;
virtual void do_rotate( Quaternion const& q ) const = 0;
virtual void do_draw() const = 0;
// ...
};
// ...
T object;
};
std::unique_ptr<Concept> pimpl;
49
friend void translate( Shape& shape, Vector3D const& v )
// ... getCenter(), getRotation(), ...
private:
double side;
OCP: A Type-Erased Approach
};
// ... Remaining data members
class Shape
{
private:
struct Concept
{
virtual ~Concept() {}
virtual void do_translate( Vector3D const& v ) const = 0;
virtual void do_rotate( Quaternion const& q ) const = 0;
virtual void do_draw() const = 0;
// ...
};
// ...
T object;
};
std::unique_ptr<Concept> pimpl;
public:
template< typename T >
Shape( T const& x )
: pimpl{ new Model<T>( x ) }
{}
// ...
T object;
};
std::unique_ptr<Concept> pimpl;
public:
template< typename T >
Shape( T const& x )
: pimpl{ new Model<T>( x ) }
{}
public:
template< typename T >
Shape( T const& x )
: pimpl{ new Model<T>( x ) }
{}
// ...
};
53
int main()
{
public:
template< typename T >
// ...
};
int main()
{
using Shapes = std::vector<Shape>;
// ...
int main()
{
using Shapes = std::vector<Shape>;
55
56
Design Evaluation
Addition of Addition of Separation of
Ease of Use Performance
shapes (OCP) operations (OCP) Concerns (SRP)
Enum 1 7 5 6 9
OO
OO 8 2 2 6 6
Visitor 2 8 8 3 1
mpark::variant 3 9 9 9 9
Strategy 7 2 7 4 2
std::function 8 3 7 7 5
Type Erasure 9 4 8 8 6
Enum 1 7 5 6 9
OO
OO 8 2 2 6 6
Visitor 2 8 8 3 1
mpark::variant 3 9 9 9 9
Strategy 7 2 7 4 2
std::function 8 3 7 7 5
Type Erasure 9 4 8 8 6
OO 8 2 2 6 6
Type Erasure 9 4 8 8 6
class Circle
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
private:
double radius;
// ... Remaining data members
};
class Square
{
public:
explicit Square( double s )
: side{ s }
, // ... Remaining data members
{} 60
61
The Open-Closed Principle (OCP)
namespace std {
return dest;
}
} // namespace std
62
The Open-Closed Principle (OCP)
63
The Liskov Substitution Principle (LSP)
64
The Liskov Substitution Principle (LSP)
Or in simplified form:
65
The Liskov Substitution Principle (LSP)
66
The Liskov Substitution Principle (LSP)
Which of the following two implementations would you choose?
namespace std {
return dest;
}
} // namespace std
68
The Liskov Substitution Principle (LSP)
namespace std {
return dest;
}
} // namespace std
69
The Liskov Substitution Principle (LSP)
Guideline: Make sure that inheritance is about behavior, not about data.
Guideline: Make sure that the contract of base types is adhered to.
70
The Interface Segregation Principle (ISP)
71
The Interface Segregation Principle (ISP)
72
The Interface Segregation Principle (ISP)
73
The Interface Segregation Principle (ISP)
strategy
Context Strategy
ConcreteStrategyA ConcreteStrategyB
74
The Interface Segregation Principle (ISP)
strategy
Shape DrawStrategy
DrawStrategyA DrawStrategyB
75
The Interface Segregation Principle (ISP)
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
private:
double radius;
// ... Remaining data members
std:unique_ptr<DrawStrategy> drawing;
};
private:
private:
double side;
// ... Remaining data members
std::unique_ptr<DrawStrategy> drawing;
};
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
class Circle;
class Square;
class DrawCircleStrategy
{
public:
virtual ~DrawCircleStrategy() {}
class DrawSquareStrategy
{
public:
virtual ~DrawSquareStrategy() {}
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
private:
double radius;
// ... Remaining data members
std:unique_ptr<DrawCircleStrategy> drawing;
};
83
class Square : public Shape
{
void rotate( Quaternion const& ) override;
void draw() const override;
private:
The Interface Segregation Principle (ISP)
double radius;
// ... Remaining data members
std:unique_ptr<DrawCircleStrategy> drawing;
};
private:
double side;
// ... Remaining data members
std::unique_ptr<DrawSquareStrategy> drawing;
};
namespace std {
return dest;
}
} // namespace std
85
The Interface Segregation Principle (ISP)
namespace std {
return dest;
}
} // namespace std
87
The Dependency Inversion Principle (DIP)
88
The Dependency Inversion Principle (DIP)
”The Dependency Inversion Principle (DIP) tells us that the most flexible
systems are those in which source code dependencies refer only to
abstractions, not to concretions.”
(Robert C. Martin, Clean Architecture)
89
The Dependency Inversion Principle (DIP)
90
The Dependency Inversion Principle (DIP)
High-level Low-level
User Permissions
91
The Dependency Inversion Principle (DIP)
High-level Low-level
<I>
User Permissions Permissions
92
The Dependency Inversion Principle (DIP)
High-level Low-level
Entities Authorizer
<I>
User Permissions Permissions
93
The Dependency Inversion Principle (DIP)
High-level Low-level
Entities Authorizer
<I>
User Permissions Permissions
94
The Dependency Inversion Principle (DIP)
95
The Dependency Inversion Principle (DIP)
Model
Controller View
96
The Dependency Inversion Principle (DIP)
Inversion of
Model Dependencies!
Dependencies Dependencies
Architectural
Boundary
Controller View
97
The Dependency Inversion Principle (DIP)
Model
Dependencies Dependencies
<Interface> <Interface>
Architectural
Boundary
Controller View
98
The Dependency Inversion Principle (DIP)
namespace std {
return dest;
}
} // namespace std
99
The Dependency Inversion Principle (DIP)
namespace std {
return dest;
}
} // namespace std
100
The Dependency Inversion Principle (DIP)
101
The SOLID Principles
Single-Responsibility Principle
Open-Closed Principle
102
Summary
103
The SOLID
Principles
Klaus Iglberger, Core C++, May 2020
klaus.iglberger@gmx.de