Breaking Dependencies:
The SOLID Principles
Klaus Iglberger, CppCon 2020
klaus.iglberger@gmx.de
2
Klaus Iglberger
C++ Trainer since 2016
Author of the C++ math library
(Co-)Organizer of the Munich C++ user group
Regular presenter at C++ conferences
Email: klaus.iglberger@gmx.de
Software
3
Software
4
Soft
=
5
Easy to change and extend
6
”Coupling is the enemy of change, because it links together things that
must change in parallel.”
(David Thomas, Andrew Hunt, The Pragmatic Programmer)
7
”Dependency is the key problem in software development at all scales.”
(Kent Beck, TDD by Example)
8
The SOLID Principles
Single-Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
9
The SOLID Principles
Single-Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Robert C. Martin Michael Feathers
10
The SOLID Principles
11
The SOLID Principles
I will introduce the SOLID principles …
… as guidelines not limited to OO programming
… as general set of guidelines
12
The Single-Responsibility Principle (SRP)
13
The Single-Responsibility Principle (SRP)
”The single responsibility principle states that every module or class
should have responsibility over a single part of the functionality
provided by the software, and that responsibility should be entirely
encapsulated by the class, module or function. All its services should be
narrowly aligned with that responsibility.”
(Wikipedia)
14
The Single-Responsibility Principle (SRP)
”Everything should do just one thing.”
(Common knowledge?)
15
The Single-Responsibility Principle (SRP)
”Orthogonality: … We want to design components that are self-
contained: independent, and with a single, well-defined purpose ([...]
cohesion). When components are isolated from one another, you know
that you can change one without having to worry about the rest.”
(Andrew Hunt, David Thomas, The Pragmatic Programmer)
16
The Single-Responsibility Principle (SRP)
”Cohesion is a measure of the strength of association of the elements
inside a module. A highly cohesive module is a collection of statements
and data items that should be treated as a whole because they are so
closely related. Any attempt to divide them up would only result in
increased coupling and decreased readability.”
(Tom DeMarco, Structured Analysis and System Specification)
17
The Single-Responsibility Principle (SRP)
”A class should have only one reason to change.”
(Robert C. Martin, Agile Software Development)
18
The Single-Responsibility Principle (SRP)
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
double getRadius() const noexcept;
getCenter(), getRotation(), …
void translate( Vector3D const& );
void rotate( Quaternion const& );
private:
double radius;
// ... Remaining data members
class Circle
{
public:
void draw( Screen& s, /*...*/ );
void draw( Printer& p, /*...*/ );
void serialize( ByteStream& bs, /*...*/ );
};
// ...
// ...
19
The Single-Responsibility Principle (SRP)
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
double getRadius() const noexcept;
getCenter(), getRotation(), …
void translate( Vector3D const& );
void rotate( Quaternion const& );
private:
double radius;
// ... Remaining data members
class Circle
{
public:
void draw( Screen& s, /*...*/ );
void draw( Printer& p, /*...*/ );
void serialize( ByteStream& bs, /*...*/ );
};
// ...
// ...
20
The Single-Responsibility Principle (SRP)
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;
…
class Circle
{
public:
void draw( Screen& s, /*...*/ );
void draw( Printer& p, /*...*/ );
void serialize( ByteStream& bs, /*...*/ );
};
// ...
// ...
21
The Single-Responsibility Principle (SRP)
Circle
Overlap
Screen
Square
Printer ByteStream
22
The Single-Responsibility Principle (SRP)
Square
Overlap
Circle Screen
Drawing
Printer ByteStream
Printing Serialization
23
The Single-Responsibility Principle (SRP)
The design of the STL follows the SRP
std::vector follows the SRP
std::string does not follow the SRP
A function should not implement details of two orthogonal issues
void BankAccount::withdrawMoney( User user, long amount )
{
// Verify access for given user
// ...
// Verify balance in account
// ...
// Update amount in database
// ...
return;
}
24
The Single-Responsibility Principle (SRP)
The design of the STL follows the SRP
std::vector follows the SRP
std::string does not follow the SRP
A function should not implement details of two orthogonal issues
void BankAccount::withdrawMoney( User user, long amount )
{
// Verify access for given user
verifyAccess( user );
// Verify balance in account
verifyBalance();
// Update balance in database
updateBalance( amount );
return;
}
25
The Single-Responsibility Principle (SRP)
Guideline: Prefer cohesive software entities. Everything that does not
strictly belong together, should be separated.
26
Questions?
27
The Open-Closed Principle (OCP)
28
The Open-Closed Principle (OCP)
”Software artifacts (classes, modules, functions, etc.) should be open
for extension, but closed for modification.”
(Bertrand Meyer, Object-Oriented Software Construction)
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{} 29
OCP: A Procedural Approach
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{} 30
OCP: A Procedural Approach
enum ShapeType
{
circle,
square
};
class Shape
{
public:
explicit Shape( ShapeType t )
, type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{} 31
OCP: A Procedural Approach
explicit Shape( ShapeType t )
, type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double radius;
// ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );
class Square : public Shape
{
public:
explicit Square( double s )
: Shape{ square }
32
OCP: A Procedural Approach
// ... getCenter(), getRotation(), ...
private:
double radius;
// ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );
class Square : public Shape
{
public:
explicit Square( double s )
: Shape{ square }
, side{ s }
, // ... Remaining data members
{}
virtual ~Square() = default;
double getSide() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double side;
// ... Remaining data members
};
void translate( Square&, Vector3D const& );
void rotate( Square&, Quaternion const& );
void draw( Square const& );
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
switch ( s->getType() )
{
33
OCP: A Procedural Approach
{}
virtual ~Square() = default;
double getSide() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double side;
// ... Remaining data members
};
void translate( Square&, Vector3D const& );
void rotate( Square&, Quaternion const& );
void draw( Square const& );
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
switch ( s->getType() )
{
case circle:
draw( *static_cast<Circle const*>( s.get() ) );
break;
case square:
draw( *static_cast<Square const*>( s.get() ) );
break;
}
}
}
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
// Creating some shapes
Shapes shapes;
shapes.push_back( std::make_unique<Circle>( 2.0 ) );
shapes.push_back( std::make_unique<Square>( 1.5 ) );
34
OCP: A Procedural Approach
{
for( auto const& s : shapes )
{
switch ( s->getType() )
{
case circle:
draw( *static_cast<Circle const*>( s.get() ) );
break;
case square:
draw( *static_cast<Square const*>( s.get() ) );
break;
}
}
}
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
// Creating some shapes
Shapes shapes;
shapes.push_back( std::make_unique<Circle>( 2.0 ) );
shapes.push_back( std::make_unique<Square>( 1.5 ) );
shapes.push_back( std::make_unique<Circle>( 4.2 ) );
// Drawing all shapes
draw( shapes );
}
35
OCP: A Procedural Approach
explicit Shape( ShapeType t )
, type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double radius;
// ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );
class Square : public Shape
{
public:
explicit Square( double s )
: Shape{ square }
36
OCP: A Procedural Approach
37
enum ShapeType
{
circle,
square,
rectangle
};
class Shape
{
public:
explicit Shape( ShapeType t )
: type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{}
38
OCP: A Procedural Approach
explicit Shape( ShapeType t )
: type{ t }
{}
virtual ~Shape() = default;
ShapeType getType() const noexcept;
private:
ShapeType type;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: Shape{ circle }
, radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double radius;
// ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );
class Square : public Shape
{
public:
explicit Square( double s )
: Shape{ square }
39
OCP: A Procedural Approach
// ... getCenter(), getRotation(), ...
private:
double radius;
// ... Remaining data members
};
void translate( Circle&, Vector3D const& );
void rotate( Circle&, Quaternion const& );
void draw( Circle const& );
class Square : public Shape
{
public:
explicit Square( double s )
: Shape{ square }
, side{ s }
, // ... Remaining data members
{}
virtual ~Square() = default;
double getSide() const noexcept;
// ... getCenter(), getRotation(), ...
private:
double side;
// ... Remaining data members
};
void translate( Square&, Vector3D const& );
void rotate( Square&, Quaternion const& );
void draw( Square const& );
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
switch ( s->getType() )
{
40
OCP: A Procedural Approach
// ... getCenter(), getRotation(), ...
private:
double side;
// ... Remaining data members
};
void translate( Square&, Vector3D const& );
void rotate( Square&, Quaternion const& );
void draw( Square const& );
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
switch ( s->getType() )
{
case circle:
draw( *static_cast<Circle const*>( s.get() ) );
break;
case square:
draw( *static_cast<Square const*>( s.get() ) );
break;
case rectangle:
draw( *static_cast<Rectangle const*>( s.get() ) );
break;
}
}
}
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
// Creating some shapes
Shapes shapes;
shapes.push_back( std::make_unique<Circle>( 2.0 ) );
shapes.push_back( std::make_unique<Square>( 1.5 ) );
41
OCP: A Procedural Approach
42
The Open-Closed Principle (OCP)
”This kind of type-based programming has a long history in C, and one of
the things we know about it is that it yields programs that are
essentially unmaintainable.”
(Scott Meyers, More Effective C++)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
43
OCP: An Object-Oriented Approach
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
44
OCP: An Object-Oriented Approach
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double radius;
// ... Remaining data members
};
class Square : public Shape
{
public:
explicit Square( double s )
45
OCP: An Object-Oriented Approach
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double radius;
// ... Remaining data members
};
class Square : public Shape
{
public:
explicit Square( double s )
: side{ s }
, // ... Remaining data members
{}
virtual ~Square() = default;
double getSide() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double side;
// ... Remaining data members
};
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
s->draw()
46
OCP: An Object-Oriented Approach
, // ... Remaining data members
{}
virtual ~Square() = default;
double getSide() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double side;
// ... Remaining data members
};
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
s->draw()
}
}
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
// Creating some shapes
Shapes shapes;
shapes.push_back( std::make_unique<Circle>( 2.0 ) );
shapes.push_back( std::make_unique<Square>( 1.5 ) );
shapes.push_back( std::make_unique<Circle>( 4.2 ) );
// Drawing all shapes
draw( shapes );
}
47
OCP: An Object-Oriented Approach
void draw() const override;
private:
double side;
// ... Remaining data members
};
void draw( std::vector<std::unique_ptr<Shape>> const& shapes )
{
for( auto const& s : shapes )
{
s->draw()
}
}
int main()
{
using Shapes = std::vector<std::unique_ptr<Shape>>;
// Creating some shapes
Shapes shapes;
shapes.push_back( std::make_unique<Circle>( 2.0 ) );
shapes.push_back( std::make_unique<Square>( 1.5 ) );
shapes.push_back( std::make_unique<Circle>( 4.2 ) );
// Drawing all shapes
draw( shapes );
}
48
OCP: An Object-Oriented Approach
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
49
OCP: An Object-Oriented Approach
50
51
Design Evaluation
Addition of
shapes (OCP)
Addition of
operations (OCP)
Separation of
Concerns (SRP)
Ease of Use Performance
Enum 1 7 5 6 9
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
1 = very bad, …, 9 = very good
Type Erasure 9 4 8 8 6
OO 8 2 2 6 6
52
53
The Open-Closed Principle (OCP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
The copy() function works for all copyable types. It …
… works for all types that adhere to the required concepts;
… does not have to be modified for new types.
54
The Open-Closed Principle (OCP)
Guideline: Prefer software design that allows the addition of types or
operations without the need to modify existing code.
55
Questions?
56
The Liskov Substitution Principle (LSP)
57
The Liskov Substitution Principle (LSP)
”What is wanted here is something like the following substitution
property: If for each object o1 of type S there is an object o2 of type T
such that for all programs P defined in terms of T, the behavior of P is
unchanged when o1 is substituted for o2 then S is a subtype of T.”
(Barbara Liskov, Data Abstraction and Hierarchy)
Or in simplified form:
”Subtypes must be substitutable for their base types.”
58
The Liskov Substitution Principle (LSP)
Behavioral subtyping (aka “IS-A” relationship)
• Contravariance of method arguments in a subtype
• Covariance of return types in a subtype
• Preconditions cannot be strengthened in a subtype
• Postconditions cannot be weakened in a subtype
• Invariants of the super type must be preserved in a subtype
59
The Liskov Substitution Principle (LSP)
Which of the following two implementations would you choose?
//***** Option A *****
class Square
{
public:
virtual void setWidth(double);
virtual int getArea();
// ...
private:
double width;
};
class Rectangle
: public Square
{
public:
virtual void setHeight(double);
// ...
private:
double height;
};
//***** Option B *****
class Rectangle
{
public:
virtual void setWidth(double);
virtual void setHeight(double);
virtual int getArea();
// ...
private:
double width;
double height;
};
class Square
: public Rectangle
{
// ...
};
60
The Liskov Substitution Principle (LSP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
61
The Liskov Substitution Principle (LSP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
The copy() function works if …
… the given InputIt adheres to the required concept;
… the given OutputIt adheres to the required concept.
62
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.
Guideline: Make sure to adhere to the required concept.
63
Questions?
64
The Interface Segregation Principle (ISP)
65
The Interface Segregation Principle (ISP)
”Clients should not be forced to depend on methods that they do not
use.”
(Robert C. Martin, Agile Software Development)
66
The Interface Segregation Principle (ISP)
”Many client specific interfaces are better than one general-purpose
interface.”
(Wikipedia)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad )
: radius{ rad }
, // ... Remaining data members
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
67
The Interface Segregation Principle (ISP)
68
The Interface Segregation Principle (ISP)
Context
context()
Strategy
virtual algorithm() = 0
ConcreteStrategyA
virtual algorithm()
ConcreteStrategyB
strategy
virtual algorithm()
69
The Interface Segregation Principle (ISP)
Shape
draw()
DrawStrategy
virtual draw() = 0
DrawStrategyA
virtual draw()
DrawStrategyB
strategy
virtual draw()
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
: radius{ rad }
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
virtual void draw( const Circle& circle ) const = 0;
virtual void draw( const Square& square ) const = 0;
};
70
The Interface Segregation Principle (ISP)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
: radius{ rad }
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
virtual void draw( const Circle& circle ) const = 0;
virtual void draw( const Square& square ) const = 0;
};
71
The Interface Segregation Principle (ISP)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
: radius{ rad }
, // ... Remaining data members
, drawing{ std::move(ds) }
{}
virtual ~Circle() = default;
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
virtual void draw( const Circle& circle ) const = 0;
virtual void draw( const Square& square ) const = 0;
};
72
The Interface Segregation Principle (ISP)
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
: radius{ rad }
, // ... Remaining data members
, drawing{ std::move(ds) }
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double radius;
// ... Remaining data members
std:unique_ptr<DrawStrategy> drawing;
};
class Square : public Shape
{
public:
73
The Interface Segregation Principle (ISP)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds )
: radius{ rad }
class Circle;
class Square;
class DrawStrategy
{
public:
virtual ~DrawStrategy() {}
virtual void draw( const Circle& circle ) const = 0;
virtual void draw( const Square& square ) const = 0;
};
74
The Interface Segregation Principle (ISP)
class Shape
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle;
class Square;
class DrawCircleStrategy
{
public:
virtual ~DrawCircleStrategy() {}
virtual void draw( const Circle& circle ) const = 0;
};
class DrawSquareStrategy
{
public:
virtual ~DrawSquareStrategy() {}
virtual void draw( const Square& square ) const = 0;
};
75
The Interface Segregation Principle (ISP)
{
public:
Shape() = default;
virtual ~Shape() = default;
virtual void translate( Vector3D const& ) = 0;
virtual void rotate( Quaternion const& ) = 0;
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
explicit Circle( double rad, std::unique_ptr<DrawCircleStrategy> ds )
: radius{ rad }
, // ... Remaining data members
, drawing{ std::move(ds) }
{}
virtual ~Circle() = default;
double getRadius() const noexcept;
// ... getCenter(), getRotation(), ...
void translate( Vector3D const& ) override;
void rotate( Quaternion const& ) override;
void draw() const override;
private:
double radius;
// ... Remaining data members
std:unique_ptr<DrawCircleStrategy> drawing;
};
class Square : public Shape
{
76
The Interface Segregation Principle (ISP)
77
The Interface Segregation Principle (ISP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
78
The Interface Segregation Principle (ISP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
The copy() function …
… only requires InputIt (minimum requirements) and …
… only requires OutputIt (minimum requirements)
… and by that imposes minimum dependencies.
79
The Interface Segregation Principle (ISP)
Guideline: Make sure interfaces don’t induce unnecessary dependencies.
80
Questions?
81
The Dependency Inversion Principle (DIP)
82
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)
83
The Dependency Inversion Principle (DIP)
”a. High-level modules should not depend on low-level modules. Both
should depend on abstractions.
b. Abstractions should not depend on details. Details should depend on
abstractions.”
(Robert C. Martin, Agile Software Development)
Drawing
Geometry
84
The Dependency Inversion Principle (DIP)
Circle DrawCircle
High-level Low-level
Drawing
Geometry
85
The Dependency Inversion Principle (DIP)
DrawCircle
DrawCircle
<I>
Circle
High-level Low-level
Local Inversion of
Dependencies
86
The Dependency Inversion Principle (DIP)
Drawing
Geometry
Circle DrawCircle
<I>
DrawCircle
High-level Low-level
87
The Dependency Inversion Principle (DIP)
88
The Dependency Inversion Principle (DIP)
Model
View
Controller
89
The Dependency Inversion Principle (DIP)
Model
Dependencies
View
Dependencies
Controller
Architectural
Boundary
Inversion of
Dependencies!
90
The Dependency Inversion Principle (DIP)
Model
<Interface> <Interface>
Dependencies
View
Dependencies
Controller
Architectural
Boundary
91
The Dependency Inversion Principle (DIP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
92
The Dependency Inversion Principle (DIP)
namespace std {
template< typename InputIt, typename OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt dest )
{
for( ; first != last; ++first, ++dest ) {
*dest = *first;
}
return dest;
}
} // namespace std
The copy() function …
… is in control of its own requirements (concepts);
… is implemented in terms of these requirements;
… you depend on copy(), not copy() on you (dependency inversion).
93
The Dependency Inversion Principle (DIP)
Guideline: Prefer to depend on abstractions (i.e. abstract classes or
concepts) instead of concrete types.
94
The SOLID Principles
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Single-Responsibility Principle
95
Summary
The SOLID principles are more than just a set of OO guidelines
Use the SOLID principles to reduce coupling and facilitate change
Separate concerns via the SRP to isolate changes
Design by OCP to simplify additions/extensions
Adhere to the LSP when using abstractions
Minimize the dependencies of interfaces via the ISP
Introduce abstractions to steer dependencies (DIP)
Breaking Dependencies:
The SOLID Principles
Klaus Iglberger, CppCon 2020
klaus.iglberger@gmx.de

More Related Content

PPT
OO Design with C++: 5. Design Principles, part 2
PPTX
Object Oriented Programming - Basic Concepts
PDF
Object-oriented design principles
PDF
Pavlo Zhdanov "Mastering solid and base principles for software design"
PDF
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
PDF
Solid principles - Object oriendte
PPTX
An Introduction to the SOLID Principles
PPT
Design poo my_jug_en_ppt
OO Design with C++: 5. Design Principles, part 2
Object Oriented Programming - Basic Concepts
Object-oriented design principles
Pavlo Zhdanov "Mastering solid and base principles for software design"
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Solid principles - Object oriendte
An Introduction to the SOLID Principles
Design poo my_jug_en_ppt

Similar to breaking_dependencies_the_solid_principles__klaus_iglberger__cppcon_2020.pdf (20)

PPTX
Solid Principles
PDF
PDF
Solid Principle
PDF
SOLID Design Principle
PPT
14 class design
PPT
14 class design (1)
PPT
How much do we know about Object-Oriented Programming?
PPT
Ood and solid principles
PDF
C++ L11-Polymorphism
PPTX
Design principles - SOLID
PPTX
The software design principles
PPT
Lec15a1-Object-Oriented Development.ppt
PDF
Design Principles SOLID_ para el desarrollo de software
PPTX
An ultimate guide to SOLID Principles, developers must know.
PPT
Object Oriented Concepts and Principles
PPTX
Advance oops concepts
PDF
Understanding SOLID Principles in OOP programming
PDF
Twins: Object Oriented Programming and Functional Programming
PPT
C++ classes tutorials
PDF
Composite Pattern
Solid Principles
Solid Principle
SOLID Design Principle
14 class design
14 class design (1)
How much do we know about Object-Oriented Programming?
Ood and solid principles
C++ L11-Polymorphism
Design principles - SOLID
The software design principles
Lec15a1-Object-Oriented Development.ppt
Design Principles SOLID_ para el desarrollo de software
An ultimate guide to SOLID Principles, developers must know.
Object Oriented Concepts and Principles
Advance oops concepts
Understanding SOLID Principles in OOP programming
Twins: Object Oriented Programming and Functional Programming
C++ classes tutorials
Composite Pattern
Ad

More from VishalKumarJha10 (6)

PDF
4. The Build System _ Embedded Android.pdf
PDF
Hilman-Runtime-Power management linux .pdf
PDF
kocialkowski-overview-linux-userspace-graphics-stack.pdf
PDF
Android memory analysis Debug slides.pdf
PDF
Linux Scheduler Latest_ viresh Kumar.pdf
PDF
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
4. The Build System _ Embedded Android.pdf
Hilman-Runtime-Power management linux .pdf
kocialkowski-overview-linux-userspace-graphics-stack.pdf
Android memory analysis Debug slides.pdf
Linux Scheduler Latest_ viresh Kumar.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
Ad

Recently uploaded (20)

PDF
Salesforce Agentforce AI Implementation.pdf
PDF
MCP Security Tutorial - Beginner to Advanced
PDF
Website Design Services for Small Businesses.pdf
PPTX
Advanced SystemCare Ultimate Crack + Portable (2025)
PDF
Microsoft Office 365 Crack Download Free
PPTX
Tech Workshop Escape Room Tech Workshop
PDF
Autodesk AutoCAD Crack Free Download 2025
PDF
CCleaner 6.39.11548 Crack 2025 License Key
PDF
How AI/LLM recommend to you ? GDG meetup 16 Aug by Fariman Guliev
PPTX
Cybersecurity: Protecting the Digital World
PPTX
Why Generative AI is the Future of Content, Code & Creativity?
PDF
Topaz Photo AI Crack New Download (Latest 2025)
PDF
DNT Brochure 2025 – ISV Solutions @ D365
PDF
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...
PDF
Cost to Outsource Software Development in 2025
PDF
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
PDF
Time Tracking Features That Teams and Organizations Actually Need
PPTX
Introduction to Windows Operating System
PDF
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
PDF
The Dynamic Duo Transforming Financial Accounting Systems Through Modern Expe...
Salesforce Agentforce AI Implementation.pdf
MCP Security Tutorial - Beginner to Advanced
Website Design Services for Small Businesses.pdf
Advanced SystemCare Ultimate Crack + Portable (2025)
Microsoft Office 365 Crack Download Free
Tech Workshop Escape Room Tech Workshop
Autodesk AutoCAD Crack Free Download 2025
CCleaner 6.39.11548 Crack 2025 License Key
How AI/LLM recommend to you ? GDG meetup 16 Aug by Fariman Guliev
Cybersecurity: Protecting the Digital World
Why Generative AI is the Future of Content, Code & Creativity?
Topaz Photo AI Crack New Download (Latest 2025)
DNT Brochure 2025 – ISV Solutions @ D365
AI-Powered Threat Modeling: The Future of Cybersecurity by Arun Kumar Elengov...
Cost to Outsource Software Development in 2025
DuckDuckGo Private Browser Premium APK for Android Crack Latest 2025
Time Tracking Features That Teams and Organizations Actually Need
Introduction to Windows Operating System
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
The Dynamic Duo Transforming Financial Accounting Systems Through Modern Expe...

breaking_dependencies_the_solid_principles__klaus_iglberger__cppcon_2020.pdf

  • 1. Breaking Dependencies: The SOLID Principles Klaus Iglberger, CppCon 2020 klaus.iglberger@gmx.de
  • 2. 2 Klaus Iglberger C++ Trainer since 2016 Author of the C++ math library (Co-)Organizer of the Munich C++ user group Regular presenter at C++ conferences Email: klaus.iglberger@gmx.de
  • 6. 6 ”Coupling is the enemy of change, because it links together things that must change in parallel.” (David Thomas, Andrew Hunt, The Pragmatic Programmer)
  • 7. 7 ”Dependency is the key problem in software development at all scales.” (Kent Beck, TDD by Example)
  • 8. 8 The SOLID Principles Single-Responsibility Principle Open-Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle
  • 9. 9 The SOLID Principles Single-Responsibility Principle Open-Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle Robert C. Martin Michael Feathers
  • 11. 11 The SOLID Principles I will introduce the SOLID principles … … as guidelines not limited to OO programming … as general set of guidelines
  • 13. 13 The Single-Responsibility Principle (SRP) ”The single responsibility principle states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class, module or function. All its services should be narrowly aligned with that responsibility.” (Wikipedia)
  • 14. 14 The Single-Responsibility Principle (SRP) ”Everything should do just one thing.” (Common knowledge?)
  • 15. 15 The Single-Responsibility Principle (SRP) ”Orthogonality: … We want to design components that are self- contained: independent, and with a single, well-defined purpose ([...] cohesion). When components are isolated from one another, you know that you can change one without having to worry about the rest.” (Andrew Hunt, David Thomas, The Pragmatic Programmer)
  • 16. 16 The Single-Responsibility Principle (SRP) ”Cohesion is a measure of the strength of association of the elements inside a module. A highly cohesive module is a collection of statements and data items that should be treated as a whole because they are so closely related. Any attempt to divide them up would only result in increased coupling and decreased readability.” (Tom DeMarco, Structured Analysis and System Specification)
  • 17. 17 The Single-Responsibility Principle (SRP) ”A class should have only one reason to change.” (Robert C. Martin, Agile Software Development)
  • 18. 18 The Single-Responsibility Principle (SRP) explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} double getRadius() const noexcept; getCenter(), getRotation(), … void translate( Vector3D const& ); void rotate( Quaternion const& ); private: double radius; // ... Remaining data members class Circle { public: void draw( Screen& s, /*...*/ ); void draw( Printer& p, /*...*/ ); void serialize( ByteStream& bs, /*...*/ ); }; // ... // ...
  • 19. 19 The Single-Responsibility Principle (SRP) explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} double getRadius() const noexcept; getCenter(), getRotation(), … void translate( Vector3D const& ); void rotate( Quaternion const& ); private: double radius; // ... Remaining data members class Circle { public: void draw( Screen& s, /*...*/ ); void draw( Printer& p, /*...*/ ); void serialize( ByteStream& bs, /*...*/ ); }; // ... // ...
  • 20. 20 The Single-Responsibility Principle (SRP) 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; … class Circle { public: void draw( Screen& s, /*...*/ ); void draw( Printer& p, /*...*/ ); void serialize( ByteStream& bs, /*...*/ ); }; // ... // ...
  • 21. 21 The Single-Responsibility Principle (SRP) Circle Overlap Screen Square Printer ByteStream
  • 22. 22 The Single-Responsibility Principle (SRP) Square Overlap Circle Screen Drawing Printer ByteStream Printing Serialization
  • 23. 23 The Single-Responsibility Principle (SRP) The design of the STL follows the SRP std::vector follows the SRP std::string does not follow the SRP A function should not implement details of two orthogonal issues void BankAccount::withdrawMoney( User user, long amount ) { // Verify access for given user // ... // Verify balance in account // ... // Update amount in database // ... return; }
  • 24. 24 The Single-Responsibility Principle (SRP) The design of the STL follows the SRP std::vector follows the SRP std::string does not follow the SRP A function should not implement details of two orthogonal issues void BankAccount::withdrawMoney( User user, long amount ) { // Verify access for given user verifyAccess( user ); // Verify balance in account verifyBalance(); // Update balance in database updateBalance( amount ); return; }
  • 25. 25 The Single-Responsibility Principle (SRP) Guideline: Prefer cohesive software entities. Everything that does not strictly belong together, should be separated.
  • 28. 28 The Open-Closed Principle (OCP) ”Software artifacts (classes, modules, functions, etc.) should be open for extension, but closed for modification.” (Bertrand Meyer, Object-Oriented Software Construction)
  • 29. enum ShapeType { circle, square }; class Shape { public: explicit Shape( ShapeType t ) : type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} 29 OCP: A Procedural Approach
  • 30. enum ShapeType { circle, square }; class Shape { public: explicit Shape( ShapeType t ) : type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} 30 OCP: A Procedural Approach
  • 31. enum ShapeType { circle, square }; class Shape { public: explicit Shape( ShapeType t ) , type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} 31 OCP: A Procedural Approach
  • 32. explicit Shape( ShapeType t ) , type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... private: double radius; // ... Remaining data members }; void translate( Circle&, Vector3D const& ); void rotate( Circle&, Quaternion const& ); void draw( Circle const& ); class Square : public Shape { public: explicit Square( double s ) : Shape{ square } 32 OCP: A Procedural Approach
  • 33. // ... getCenter(), getRotation(), ... private: double radius; // ... Remaining data members }; void translate( Circle&, Vector3D const& ); void rotate( Circle&, Quaternion const& ); void draw( Circle const& ); class Square : public Shape { public: explicit Square( double s ) : Shape{ square } , side{ s } , // ... Remaining data members {} virtual ~Square() = default; double getSide() const noexcept; // ... getCenter(), getRotation(), ... private: double side; // ... Remaining data members }; void translate( Square&, Vector3D const& ); void rotate( Square&, Quaternion const& ); void draw( Square const& ); void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { switch ( s->getType() ) { 33 OCP: A Procedural Approach
  • 34. {} virtual ~Square() = default; double getSide() const noexcept; // ... getCenter(), getRotation(), ... private: double side; // ... Remaining data members }; void translate( Square&, Vector3D const& ); void rotate( Square&, Quaternion const& ); void draw( Square const& ); void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { switch ( s->getType() ) { case circle: draw( *static_cast<Circle const*>( s.get() ) ); break; case square: draw( *static_cast<Square const*>( s.get() ) ); break; } } } int main() { using Shapes = std::vector<std::unique_ptr<Shape>>; // Creating some shapes Shapes shapes; shapes.push_back( std::make_unique<Circle>( 2.0 ) ); shapes.push_back( std::make_unique<Square>( 1.5 ) ); 34 OCP: A Procedural Approach
  • 35. { for( auto const& s : shapes ) { switch ( s->getType() ) { case circle: draw( *static_cast<Circle const*>( s.get() ) ); break; case square: draw( *static_cast<Square const*>( s.get() ) ); break; } } } int main() { using Shapes = std::vector<std::unique_ptr<Shape>>; // Creating some shapes Shapes shapes; shapes.push_back( std::make_unique<Circle>( 2.0 ) ); shapes.push_back( std::make_unique<Square>( 1.5 ) ); shapes.push_back( std::make_unique<Circle>( 4.2 ) ); // Drawing all shapes draw( shapes ); } 35 OCP: A Procedural Approach
  • 36. explicit Shape( ShapeType t ) , type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... private: double radius; // ... Remaining data members }; void translate( Circle&, Vector3D const& ); void rotate( Circle&, Quaternion const& ); void draw( Circle const& ); class Square : public Shape { public: explicit Square( double s ) : Shape{ square } 36 OCP: A Procedural Approach
  • 37. 37
  • 38. enum ShapeType { circle, square, rectangle }; class Shape { public: explicit Shape( ShapeType t ) : type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} 38 OCP: A Procedural Approach
  • 39. explicit Shape( ShapeType t ) : type{ t } {} virtual ~Shape() = default; ShapeType getType() const noexcept; private: ShapeType type; }; class Circle : public Shape { public: explicit Circle( double rad ) : Shape{ circle } , radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... private: double radius; // ... Remaining data members }; void translate( Circle&, Vector3D const& ); void rotate( Circle&, Quaternion const& ); void draw( Circle const& ); class Square : public Shape { public: explicit Square( double s ) : Shape{ square } 39 OCP: A Procedural Approach
  • 40. // ... getCenter(), getRotation(), ... private: double radius; // ... Remaining data members }; void translate( Circle&, Vector3D const& ); void rotate( Circle&, Quaternion const& ); void draw( Circle const& ); class Square : public Shape { public: explicit Square( double s ) : Shape{ square } , side{ s } , // ... Remaining data members {} virtual ~Square() = default; double getSide() const noexcept; // ... getCenter(), getRotation(), ... private: double side; // ... Remaining data members }; void translate( Square&, Vector3D const& ); void rotate( Square&, Quaternion const& ); void draw( Square const& ); void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { switch ( s->getType() ) { 40 OCP: A Procedural Approach
  • 41. // ... getCenter(), getRotation(), ... private: double side; // ... Remaining data members }; void translate( Square&, Vector3D const& ); void rotate( Square&, Quaternion const& ); void draw( Square const& ); void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { switch ( s->getType() ) { case circle: draw( *static_cast<Circle const*>( s.get() ) ); break; case square: draw( *static_cast<Square const*>( s.get() ) ); break; case rectangle: draw( *static_cast<Rectangle const*>( s.get() ) ); break; } } } int main() { using Shapes = std::vector<std::unique_ptr<Shape>>; // Creating some shapes Shapes shapes; shapes.push_back( std::make_unique<Circle>( 2.0 ) ); shapes.push_back( std::make_unique<Square>( 1.5 ) ); 41 OCP: A Procedural Approach
  • 42. 42 The Open-Closed Principle (OCP) ”This kind of type-based programming has a long history in C, and one of the things we know about it is that it yields programs that are essentially unmaintainable.” (Scott Meyers, More Effective C++)
  • 43. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: 43 OCP: An Object-Oriented Approach
  • 44. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: 44 OCP: An Object-Oriented Approach
  • 45. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double radius; // ... Remaining data members }; class Square : public Shape { public: explicit Square( double s ) 45 OCP: An Object-Oriented Approach
  • 46. // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double radius; // ... Remaining data members }; class Square : public Shape { public: explicit Square( double s ) : side{ s } , // ... Remaining data members {} virtual ~Square() = default; double getSide() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double side; // ... Remaining data members }; void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { s->draw() 46 OCP: An Object-Oriented Approach
  • 47. , // ... Remaining data members {} virtual ~Square() = default; double getSide() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double side; // ... Remaining data members }; void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { s->draw() } } int main() { using Shapes = std::vector<std::unique_ptr<Shape>>; // Creating some shapes Shapes shapes; shapes.push_back( std::make_unique<Circle>( 2.0 ) ); shapes.push_back( std::make_unique<Square>( 1.5 ) ); shapes.push_back( std::make_unique<Circle>( 4.2 ) ); // Drawing all shapes draw( shapes ); } 47 OCP: An Object-Oriented Approach
  • 48. void draw() const override; private: double side; // ... Remaining data members }; void draw( std::vector<std::unique_ptr<Shape>> const& shapes ) { for( auto const& s : shapes ) { s->draw() } } int main() { using Shapes = std::vector<std::unique_ptr<Shape>>; // Creating some shapes Shapes shapes; shapes.push_back( std::make_unique<Circle>( 2.0 ) ); shapes.push_back( std::make_unique<Square>( 1.5 ) ); shapes.push_back( std::make_unique<Circle>( 4.2 ) ); // Drawing all shapes draw( shapes ); } 48 OCP: An Object-Oriented Approach
  • 49. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: 49 OCP: An Object-Oriented Approach
  • 50. 50
  • 51. 51 Design Evaluation Addition of shapes (OCP) Addition of operations (OCP) Separation of Concerns (SRP) Ease of Use Performance Enum 1 7 5 6 9 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 1 = very bad, …, 9 = very good Type Erasure 9 4 8 8 6 OO 8 2 2 6 6
  • 52. 52
  • 53. 53 The Open-Closed Principle (OCP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std The copy() function works for all copyable types. It … … works for all types that adhere to the required concepts; … does not have to be modified for new types.
  • 54. 54 The Open-Closed Principle (OCP) Guideline: Prefer software design that allows the addition of types or operations without the need to modify existing code.
  • 56. 56 The Liskov Substitution Principle (LSP)
  • 57. 57 The Liskov Substitution Principle (LSP) ”What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.” (Barbara Liskov, Data Abstraction and Hierarchy) Or in simplified form: ”Subtypes must be substitutable for their base types.”
  • 58. 58 The Liskov Substitution Principle (LSP) Behavioral subtyping (aka “IS-A” relationship) • Contravariance of method arguments in a subtype • Covariance of return types in a subtype • Preconditions cannot be strengthened in a subtype • Postconditions cannot be weakened in a subtype • Invariants of the super type must be preserved in a subtype
  • 59. 59 The Liskov Substitution Principle (LSP) Which of the following two implementations would you choose? //***** Option A ***** class Square { public: virtual void setWidth(double); virtual int getArea(); // ... private: double width; }; class Rectangle : public Square { public: virtual void setHeight(double); // ... private: double height; }; //***** Option B ***** class Rectangle { public: virtual void setWidth(double); virtual void setHeight(double); virtual int getArea(); // ... private: double width; double height; }; class Square : public Rectangle { // ... };
  • 60. 60 The Liskov Substitution Principle (LSP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std
  • 61. 61 The Liskov Substitution Principle (LSP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std The copy() function works if … … the given InputIt adheres to the required concept; … the given OutputIt adheres to the required concept.
  • 62. 62 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. Guideline: Make sure to adhere to the required concept.
  • 64. 64 The Interface Segregation Principle (ISP)
  • 65. 65 The Interface Segregation Principle (ISP) ”Clients should not be forced to depend on methods that they do not use.” (Robert C. Martin, Agile Software Development)
  • 66. 66 The Interface Segregation Principle (ISP) ”Many client specific interfaces are better than one general-purpose interface.” (Wikipedia)
  • 67. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad ) : radius{ rad } , // ... Remaining data members {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: 67 The Interface Segregation Principle (ISP)
  • 68. 68 The Interface Segregation Principle (ISP) Context context() Strategy virtual algorithm() = 0 ConcreteStrategyA virtual algorithm() ConcreteStrategyB strategy virtual algorithm()
  • 69. 69 The Interface Segregation Principle (ISP) Shape draw() DrawStrategy virtual draw() = 0 DrawStrategyA virtual draw() DrawStrategyB strategy virtual draw()
  • 70. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds ) : radius{ rad } class Circle; class Square; class DrawStrategy { public: virtual ~DrawStrategy() {} virtual void draw( const Circle& circle ) const = 0; virtual void draw( const Square& square ) const = 0; }; 70 The Interface Segregation Principle (ISP)
  • 71. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds ) : radius{ rad } class Circle; class Square; class DrawStrategy { public: virtual ~DrawStrategy() {} virtual void draw( const Circle& circle ) const = 0; virtual void draw( const Square& square ) const = 0; }; 71 The Interface Segregation Principle (ISP)
  • 72. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds ) : radius{ rad } , // ... Remaining data members , drawing{ std::move(ds) } {} virtual ~Circle() = default; class Circle; class Square; class DrawStrategy { public: virtual ~DrawStrategy() {} virtual void draw( const Circle& circle ) const = 0; virtual void draw( const Square& square ) const = 0; }; 72 The Interface Segregation Principle (ISP)
  • 73. public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds ) : radius{ rad } , // ... Remaining data members , drawing{ std::move(ds) } {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double radius; // ... Remaining data members std:unique_ptr<DrawStrategy> drawing; }; class Square : public Shape { public: 73 The Interface Segregation Principle (ISP)
  • 74. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawStrategy> ds ) : radius{ rad } class Circle; class Square; class DrawStrategy { public: virtual ~DrawStrategy() {} virtual void draw( const Circle& circle ) const = 0; virtual void draw( const Square& square ) const = 0; }; 74 The Interface Segregation Principle (ISP)
  • 75. class Shape { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle; class Square; class DrawCircleStrategy { public: virtual ~DrawCircleStrategy() {} virtual void draw( const Circle& circle ) const = 0; }; class DrawSquareStrategy { public: virtual ~DrawSquareStrategy() {} virtual void draw( const Square& square ) const = 0; }; 75 The Interface Segregation Principle (ISP)
  • 76. { public: Shape() = default; virtual ~Shape() = default; virtual void translate( Vector3D const& ) = 0; virtual void rotate( Quaternion const& ) = 0; virtual void draw() const = 0; }; class Circle : public Shape { public: explicit Circle( double rad, std::unique_ptr<DrawCircleStrategy> ds ) : radius{ rad } , // ... Remaining data members , drawing{ std::move(ds) } {} virtual ~Circle() = default; double getRadius() const noexcept; // ... getCenter(), getRotation(), ... void translate( Vector3D const& ) override; void rotate( Quaternion const& ) override; void draw() const override; private: double radius; // ... Remaining data members std:unique_ptr<DrawCircleStrategy> drawing; }; class Square : public Shape { 76 The Interface Segregation Principle (ISP)
  • 77. 77 The Interface Segregation Principle (ISP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std
  • 78. 78 The Interface Segregation Principle (ISP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std The copy() function … … only requires InputIt (minimum requirements) and … … only requires OutputIt (minimum requirements) … and by that imposes minimum dependencies.
  • 79. 79 The Interface Segregation Principle (ISP) Guideline: Make sure interfaces don’t induce unnecessary dependencies.
  • 81. 81 The Dependency Inversion Principle (DIP)
  • 82. 82 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)
  • 83. 83 The Dependency Inversion Principle (DIP) ”a. High-level modules should not depend on low-level modules. Both should depend on abstractions. b. Abstractions should not depend on details. Details should depend on abstractions.” (Robert C. Martin, Agile Software Development)
  • 84. Drawing Geometry 84 The Dependency Inversion Principle (DIP) Circle DrawCircle High-level Low-level
  • 85. Drawing Geometry 85 The Dependency Inversion Principle (DIP) DrawCircle DrawCircle <I> Circle High-level Low-level Local Inversion of Dependencies
  • 86. 86 The Dependency Inversion Principle (DIP) Drawing Geometry Circle DrawCircle <I> DrawCircle High-level Low-level
  • 87. 87 The Dependency Inversion Principle (DIP)
  • 88. 88 The Dependency Inversion Principle (DIP) Model View Controller
  • 89. 89 The Dependency Inversion Principle (DIP) Model Dependencies View Dependencies Controller Architectural Boundary Inversion of Dependencies!
  • 90. 90 The Dependency Inversion Principle (DIP) Model <Interface> <Interface> Dependencies View Dependencies Controller Architectural Boundary
  • 91. 91 The Dependency Inversion Principle (DIP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std
  • 92. 92 The Dependency Inversion Principle (DIP) namespace std { template< typename InputIt, typename OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt dest ) { for( ; first != last; ++first, ++dest ) { *dest = *first; } return dest; } } // namespace std The copy() function … … is in control of its own requirements (concepts); … is implemented in terms of these requirements; … you depend on copy(), not copy() on you (dependency inversion).
  • 93. 93 The Dependency Inversion Principle (DIP) Guideline: Prefer to depend on abstractions (i.e. abstract classes or concepts) instead of concrete types.
  • 94. 94 The SOLID Principles Open-Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle Single-Responsibility Principle
  • 95. 95 Summary The SOLID principles are more than just a set of OO guidelines Use the SOLID principles to reduce coupling and facilitate change Separate concerns via the SRP to isolate changes Design by OCP to simplify additions/extensions Adhere to the LSP when using abstractions Minimize the dependencies of interfaces via the ISP Introduce abstractions to steer dependencies (DIP)
  • 96. Breaking Dependencies: The SOLID Principles Klaus Iglberger, CppCon 2020 klaus.iglberger@gmx.de