Codementor Events

From Simple Techniques To Design Patterns in C++ —Part II

Published Jun 16, 2021Last updated Jun 21, 2021
From Simple Techniques To Design Patterns in C++ —Part II

An Introduction to Abstract Factory and Requirement Changes

The Technique To Design Patterns series aims to complement technical articles about the language itself and other Design Patterns dictionaries. I want to explain what kind of decisions can drive your design, when, and why you must consider them.

After looking at the basic pimpl in the first part of this series of articles, we will discuss how to deal with change in requirements. Then we will introduce our first design pattern: the Abstract Factory. All this with a concrete example (inspired by one of my interview questions).

I will provide a simple implementation, and we will see the benefits and constraints of the AbstractFactory. At the end of the article, you will know enough to understand when this pattern makes sense.

There is a lot of theory and many methodologies in software project management. Most of them deal with handling changes in your requirements. Let's face it: as a developer, your product is most probably a moving target. Let's start by talking about it a little.

Today's Ever-Changing Problem.

bobtheproductmanager2.png

Let me introduce one of the most influential people in your developer's life: Bob, the (fictional) Product Manager. Bob is cool. He has visions about your company's software. You both agree on some requirements for the project. But you know him very well. Bob has ideas, many ideas. And you know that Bob will come up with new features to implement quite often.

In real life, you are lucky to have the first version of the software's complete requirements when you start implementation. But you can be sure of 2 things:

  • Requirements change. If you are extra lucky, the specs are properly defined and won't change too much for the fist version of the software while you are implementing it.
  • Then there will be a new set of requirements for the second version. And things can change drastically.

As a software architect, your task is to prepare your software for those changes. It is not easy, and the frontier between over-engineering for reusability and throw away code is sometimes difficult to apprehend.

YAGNI! (Ya Ain't Gonna Need It!) is a popular expression today, and it is a good principle, don't overcomplicate things when you don't need to. A good design helps pave the way for evolution without adding complexity. It also makes your software easier to understand. When you recognize the implementation of a given design pattern, you know what, how, and why this piece of code. It helps to figure the big picture behind the code.

We will do what happens in real life and change the requirements. All the fun will be to see how to adapt our solution to those changes and anticipate the next round of requirements while making the current version more elegant. Keep in mind that I will propose multiple designs. I will highlight the strengths and weaknesses of each of them and suggest refinements.

In the great software we are writing, we have some Circles, Rectangles, and Triangles. They inherit from Shape.

We should be able to:

  • Load a set of shapes from a file (using a specified file format).
  • Render them on a window (using a specified rendering API).

The details about the graphics API are not significant for our example. The file format is as simple as possible. It is a text file. Each line contains the information for a shape with the following structure:

  • 'C' for a circle, followed by three floats: center X, Y, and radius.
  • 'R' for a rectangle: followed by upper left Point X, Y, width, and height.
  • 'T' for a triangle, followed by 3 points X and Y coordinates (6 floats).

For instance, a simple drawing with a Circle, a Rectangle, and a Triangle look like this:

C 10 10 5
R 2 2 6 4
T 3 3 3 6 5 6

The first line creates a circle with a center of (10, 10) and radius of 5.

The second line creates an axis-aligned rectangle with an upper left point (2, 2), width of 6, and height of 4.

The last line creates a triangle with points (3, 3), (3, 6), and (5, 6).

Honestly, for requirements, it is not that bad. It is pretty straightforward. In real life, many of us work with far less than that, and not as precise 😃 .

A first Solution

We have a few things to design:

  • Something to model and store our geometry.
  • A way to load it from a file.
  • A way to render it.

It seems easy. Let's start with a simple class hierarchy. The Shape is a base class from which Circles, Rectangle, and Triangle inherit.

The base class provides a pure virtual function: draw, redefined in each child class.

BasicShape1.png

I provide an implementation in a single file for brevity. I insist on splitting the code in multiple files to limit dependencies on the previous article. The most important thing is to put the Shape interface in a standalone header, as we will reuse it many times.

Here is Shape.h:

struct Shape
{
  virtual ~Shape() = default; // A virtual class needs
  // a virtual destructor.
  virtual void draw() const = 0;
};

This base class is a pure interface. Besides the Destructor, its only function is pure virtual (=0). It means that we must implement it in every child. Cool, this way, we cannot forget it, the compiler would complain.

Note that, at this point, I defined this function const. Intuitively, a rendering function should not change the state of the object it draws (it might not always be the case if there are some tricks with graphics API, cache... But for our simple example, it seems reasonable).

Then we implement child classes. Each of them comes with a constructor with specific parameters and a draw function.

Here is a simple implementation of the shapes:

#include <vector>
#include <memory>
#include <fstream>
#include <iostream>
#include <cassert>
#include <cstring>
#include <filesystem>

struct Point {
    float x;
    float y;
};

struct Shape
{
    virtual ~Shape() = default;
    virtual void draw() const = 0;
};

class Circle : public Shape
{
public:
    Circle( Point center, float r ): m_center{ center }, m_radius{ r } {}
    void draw() const {
    	std::cout << "circle (" << m_center.x << ", " << m_center.y
                                << ", " << m_radius << '\n';
    }
private:
    Point m_center;
    float m_radius;
};

class Rectangle : public Shape
{
public:
    Rectangle( Point p, float w, float h ): m_p{ p }, m_w{ w }, m_h{ h } {}
    void draw() const { 
    	std::cout << "rectangle (" << m_p.x << ", " << m_p.y
                                   << ", " << m_w << ", " << m_h << '\n';
        }
private:
    Point m_p;
    float m_w;
    float m_h;
};

class Triangle: public Shape
{
public:
    Triangle( Point p1, Point p2, Point p3 ): m_p1{ p1 }, m_p2{ p2 }, m_p3{ p3 } {}
    void draw() const {
    	std::cout << "triangle (" << m_p1.x << ", " << m_p1.y  << "), ("
                                  << m_p2.x << ", " << m_p2.y  << "), ("
                                  << m_p3.x << ", " << m_p3.y  << ")\n";
    }
private:
    Point m_p1;
    Point m_p2;
    Point m_p3;
};

//-----------------------------------------------------------------------------
std::vector< std::unique_ptr< Shape >> loadShapes( const std::string & path )
{
    // Very Naive implementation without error checking.
    std::vector< std::unique_ptr< Shape >> shapes;

    std::ifstream infile;
    infile.open(path, std::ios::in);
    if( !infile ) {
        std::cerr << "Cannot open file " << path
        << " on current working dir " << std::filesystem::current_path()
        << ": " << std::strerror(errno);
        return {};
    }
    char shapeType;

    while(infile >> shapeType )  {
        switch( shapeType ) {
            case 'C': {
                Point p;
                float r;
                infile >> p.x >> p.y >> r;
                shapes.push_back( std::make_unique<Circle>( p, r ));
                break;
            }
            case 'R': {
                Point p;
                float w, h;
                infile >> p.x >> p.y >> w >> h;
                shapes.push_back( std::make_unique<Rectangle>( p, w, h ));
                break;
            }
            case 'T': {
                Point p1, p2, p3;
                infile >> p1.x >> p1.y >> p2.x >> p2.y >> p3.x >> p3.y;
                shapes.push_back( std::make_unique<Triangle>( p1, p2, p3 ));
                break;
            }
        }
    }
    return shapes;
}

If you are not familiar with the move semantic, it might be surprising that loadDrawing returns a vector. Since C++11, this will not trigger a vector copy. The content vector created in the function loadDrawing is "moved" to the vector shapes. It is fast.

To make things shorter, I did not check for errors. Still, in an actual application, you should verify that the file is open and output an error message if it is the case (you could raise an exception and catch it in the main before exiting the program with an error code).

In the end, you would use it like this:

int main()
{
  auto shapes = loadDrawing("tests/example1.shapes");
  for( auto const &s : shapes ) {
    s->draw();
  }
}

And you're done! That was easy. But as easy as it is, this naive implementation has many problems...

Bob is Back

bobhead.png

Bob, the Product Manager, had an idea. You must identify the collection in a file with a name. Let's call it a ClipArt. For the moment, we store it in a std::vector of pointers to Shapes. Associating a name means passing it around each time to functions manipulating the ClipArt. It will not be convenient and a source of bugs.

And, knowing Bob, he will require us to store other information along with the ClipArt. A natural thing, from here, is to create a class to hold all the information about it. Let's do that:

class ClipArt 
{
public:

  ClipArt( const std::string & name ) : m_name ( name ) {}
  void addShape( std::unique_ptr< Shape > && s ) {
    m_shapes.emplace_back( std::move( s ));
    }

  const std::string & getName() { return m_name; }
  void draw() const;

private:
  const std::string m_name;
  std::vector< std::unique_ptr< Shape >> m_shapes;
};

In the cpp file, you have the following implementation:

void ClipArt::draw() const
{
  for( auto const & s : m_shapes ) s->draw();
}

Now that you have your improved ClipArt object, created with a name. Next, you need to change the loadClipArt function to return a ClipArt initialized with its name read from the shape file's first line.

The implementation now looks like this:


std::unique_ptr< ClipArt > loadClipArt( const std::string & path )
{
    std::string drawingName;
    std::ifstream infile;
    infile.open(path, std::ios::in);

    // First line of the file is the name of the ClipArt.
    std::getline(infile, drawingName);
    auto clipArt = std::make_unique< ClipArt >( drawingName );
    char shapeType;

    while(infile >> shapeType ) {
        switch( shapeType ) {
            case 'C': {
                Point p;
                float r;
                infile >> p.x >> p.y >> r;
                clipArt->addShape( std::make_unique<Circle>( p, r ));
                break;
            }

            case 'R': {
                Point p;
                float w, h;
                infile >> p.x >> p.y >> w >> h;
                clipArt->addShape( std::make_unique<Rectangle>( p, w, h ));
                break;
            }
            case 'T': {
                Point p1, p2, p3;
                infile >> p1.x >> p1.y >> p2.x >> p2.y >> p3.x >> p3.y;
                clipArt->addShape( std::make_unique<Triangle>( p1, p2, p3 ));
                break;
            }
        }
    }
    return clipArt;
}

You instantiate the drawing with a name, and you add Shapes. Still straightforward.

We already won something: if for some reason I want to store the shapes in a std::list, std::deque, or hand-made container, I can do it without changing a single line from the loading code. It is a small step in the right direction. Recall from the first article of the series that the less coupling you have, the better.

Architecture evolution1.png

In the picture, I made some green arrows to show who knows what.
It is a simple relationship with the following property: A -knows-> B implies:

  • Change in A has no impact on B.
  • Change in B means A will probably need to change as well.

One of the most important things to keep complexity under control is understanding who knows what.
In our case, we had a function that knew everything about:

  • parsing drawing text,
  • how a ClipArt is stored (as a vector of Shapes),
  • and how to build a Shape.

It is far too much! Knowing Bob, you know that he will ask for more features, and things will quickly get very complicated.
Your file, implementing the loadDrawing function, starts with many includes: one for every Shape. This is some severe code smell, and it should alert you. You are introducing dependencies that will strike you back in the future.
Our refined design is a little better. The loading function knows about ClipArt and Shapes (interface and specific Shapes), but it does not know how they are stored. All ClipArt implementation details are in the private part, they could be entirely hidden behind a pimpl (see the first article on the subject).
The funny thing is that the ClipArt itself does not need to know about the Rectangle, circle and Triangle. It only uses the Shape Interface (the header). That is very cool, as you will see later.
You can change how ClipArts are stored internally without changing a single line in the loading code.

We made an important step, but it is not enough.
There is a slight glitch in this design. The loading code knows too much about Shapes.

We could solve it quickly by moving the knowledge of the specific shapes into the ClipArt, and add the following functions:

  • addRectangle
  • addCircle
  • addTriangle

This alternative design would look like this:
Architecture evolution2.png

Well, it looks better. Now the loading function only knows about ClipArt; it does not know about shapes. It calls addRectangle, addCircle, and addTriangle.

But now, ClipArt knows about specific implementation. It is responsible for the instantiation of the shapes. Is it really a win?

To know that and decide on the best design, we need to understand what class will most probably evolve and how those changes will propagate.

We are talking about public interfaces here (or protected, if A inherits from B).
So the change propagates in the opposite direction of the knowledge arrow. Now you can see why cyclic dependencies lead to complex code, hard to maintain. Changes propagate everywhere and create problems potentially all over the project.

So now, to decide on the first or the second design, we have to answer the following questions:

  • What part of the code will be the most used by our customers?
  • What part of the code will probably change the most?
    If you think about it, the answer is not that simple. The second design seems cleaner because the loading code has fewer green arrows.

The user will probably use the loading function once or twice. But the calls to the ClipArt function will be scattered all over his code. Adding a Shape will have a more significant impact because ClipArt needs to change.

So far, I would favor the first design, but I agree, it is not perfect.
I don't want to spoil it, but there is a better way.
But wait! Bob is back with new stuff!

Now Bob Wants To Render With Multiple Libraries.

bobhead.png
Yes, Bob has a new idea. He wants to render shapes with various APIs. Let's say we have two libraries, libRenderA and libRenderB (you will substitute with your favorite rendering APIs, be it OpenGL, QtPainter, SVG...).
There are many ways to implement it, but let's start with a very naive one, and we will improve it.

Our shape interface has a draw function. Let's implement two draw functions, one for each API.
We have the following interface:

class Shape
{
...
  virtual void drawA();
  virtual void drawB();
};

And all our implementations, the Rectangle, the Circle, and the Triangle, have to implement those two functions. Of course, you need to add two equivalent functions to the ClipArt:

  • virtual void drawA();
  • virtual void drawB();

The draw function has the same code as our previous implementation, except ClipArt::DrawA calls Shape::DrawA, and ClipArt::DrawB calls Shape::DrawB.

BasicShape2.png

Easy enough and quickly implemented.
But this solution introduced some problems. Big ones.

The first problem is that your library users have to call either DrawA or DrawB to render your ClipArts. It requires modification of all the client code.

The second problem is in the implementation of the Shape inheriting objects. The Rectangle, for instance, must implement both drawA and drawB functions.

It means that it needs to include both the RenderA and the RenderB headers and link with both libraries.

Knowing Bob, he will ask for some more rendering libraries shortly, leading to dependency hell. Imagine your Rectangle.cpp file starting with includes for OpenGL, DirectX, SVG, pdf, Qt, X11 headers... and linking with all that. Provided that all those libraries are available on every platform you target. If your software must run on macOS, Windows, Linux, and Android, you will clutter your code with a hell of #ifdefs everywhere.

ShapeMadeInHell.png
just... don't do that...

And the users of your libraries will have to do the same.

There are some better ways. In this part, I will propose a simple solution. Then I will refine it with different design patterns in later articles.

Instead of putting multiple drawing functions in the Shape class, we will keep it simple and implement a different Rectangle, Circle, and Triangles to render with each rendering library.

MultiShapes1.png

Instead of having a unique Rectangle, we have:

  • RectangleA with the draw() function that will use RenderA.
  • RectangleB with the draw() function that will use RenderB.

And the same thing for CircleA, CircleB, TriangleA, and TriangleB.

The ClipArt has a single function draw() that calls draw() on its shapes.

If all the shapes of a given ClipArt are from the "A" family (RectangleA, CircleA, and TriangleA), it will use the RenderA library. Same for the "B" family. You can use this ClipArt all over the application without really caring about the detail of the rendering API, provided that it is created with all its Shapes from the same family.

So the only problem left is how we can achieve that?

Let's use an Abstract Factory!

There is a design pattern to solve our problem: the Abstract Factory. First, let's see what a simple factory function is.

Sometimes, it is impossible to create and initialize an object in the constructor of a function. There might be a lot of reasons for that. A simple one, for instance, is that your class uses inheritance, and you want your base class constructor to call a virtual function implemented in the derived class. It is not possible. You will have to do a 2 stage initialization (constructor + function init), which is, as we saw previously, a bad thing.

You might as well want to enforce that your object is created and owned by a smart pointer.

The most straightforward way to achieve that is to use a Factory function.

For instance, let's say that your class Item has a constructor + init function, and you want to force the users to use std::shared_ptr, you could:

  • Make the Item constructor Private.
  • Implement a create static function to instantiate, initialize and return a std::shared_ptr.

It would look like that:

class Item
{
public:
  static std::shared_ptr< Item > create() {
    auto ret = std::make_shared< Item >();
    ret->init();
    return ret;
  }
private:
Item();
void init();
} ;

This simple solution ticks all the boxes:

  • The user of your class cannot instantiate an Item directly. They must use the function create because the constructor is private.
  • Init is called for each Item created.
  • The only way to access an Item is through a std::shared_ptr.

It is a first step in the right direction, but not enough. We really would like the users of our Shape library to manipulate just shapes, without knowing if it belongs to A or B family. It is already the case for the ClipArt class, as it only interacts with Shapes, storing and drawing them without knowing their exact type.

But there is still a problem. The load function must know the Shape Family. You might create two functions, loadClipArtA and loadClipArtB, with almost the same code, but they will instantiate Shapes from the A or B family.

Your users must use the proper function. Easy enough? They just have to clutter their code with conditional code to call the right loadShape each time they want to load a new shape! Well, you simplified your code at the cost of some additional work spread on your client code.

You know Bob, tomorrow he will come up with yet another rendering library, and your client code will have to change everywhere they call the loadShape A or B function.

good_code.png

It seemed to be a good idea, at least initially. But now, it does not sound that engaging. There is a better way.

Introducing the Abstract Factory.

The Abstract Factory is a solution to this problem.

The idea is quite simple. Instead of implementing some create function on every Shape derived class, and somehow counting on the calling code to consistently create shapes of the A or B family, let's create a factory object to do this job.

Let's implement a Factory class for the A family first.

Its responsibilities are:

  • Create a shape of the right type (Rectangle, Circle, Triangle).
  • Create a Shape from the right family (A, in this case).
  • Provide the Shape through a std::unique_ptr.

Let's see the implementation:

struct ShapeFactoryA
{
  std::shared_ptr< Shape > createRectangle( Point p1, float w, float h ) {
    return std::make_unique< RectangleA >( p1, w, h );
  }
  std::shared_ptr< Shape > createCircle( Point p1, float r ) {
    return std::make_unique< CircleA >( p1, r );
  }
  std::shared_ptr< Shape > createRectangle( Point p1, Point p2, Point p3 ) {
    return std::make_unique< TriangleA >( p1, w, h );
  }
};

Note that the name of the class indicates its family, but the create functions names do not mention A or B.

You would change the code of loading function for the A family to look like this:

std::vector< std::unique_ptr< Shape >> loadClipArtA( const std::string & path )
{
  ShapeFactoryA factory;
  ...
  ...

  while(infile >> shapeType ) {
    switch( shapeType ) {
    case 'C': {
      Point p;
      float r;
      infile >> p.x >> p.y >> r;
      drawing->add( factory->createCircle( p, r );
      break;
    }
    case 'R': ...
    ...
    ...
}

From now, we can easily create a LoadClipArtB that will use a ShapeFactoryB.
Good! Using one Factory or the other solved some part of the problem. We are sure that our ClipArt library user will consistently use one family using the corresponding Factory.
It is a big step that will probably prevent many bugs in the future. But we did not fix the main problem: the loadClipArt function still exists in the two versions.
There is a straightforward way to solve this problem in 3 small steps.
Lets first create an interface for our factory:

struct ShapeFactory
{
  virtual ~ShapeFactory() = default;
  std::shared_ptr<Shape> createRectangle( Point p1, float w, float h ) = 0;
  std::shared_ptr<Shape> createCircle( Point p1, float r ) = 0;
  std::shared_ptr<Shape> createTriangle(Point p1, Point p2, Point p3) = 0;
};

Second step: update ShapeFactoryA and ShapeFactoryB to inherit from ShapeFactory. ShapeFactoryA would be updated this way:

#include <ShapFactory.h>

struct ShapeFactoryA : public ShapeFactory
{
... // Same functions.
};

The last thing, add a factory argument to loadClipArt:

std::vector< std::unique_ptr< Shape >>
loadClipArt( const std::string & path, ShapeFactory & factory )
{
  ...
  while(infile >> shapeType ) {
    switch( shapeType ) {
    case 'C': {
      Point p;
      float r;
      infile >> p.x >> p.y >> r;
      drawing->add( factory->createCircle( p, r );
      break;
    }
    case 'R': ...
    ...
    ...
    }
        ...

As you can see, the code has not changed a lot. Instead of instantiating the factory, it is provided as an argument. The type of this argument is a generic ShapeFactory.

You would use it this way:

ShapeFactoryA fact;
loadClipArt( "clipart.txt", fact);

Now, the unique loadClipArt function does not know the Shapes family. And it does not care. It does not even know how to create a shape!

It is now the responsibility of the Factorie to handle the shape creation the right way.

This pattern consists of having a virtual interface to a factory and concrete implementations.

But wait, there is still a problem! I did all those things to avoid having two loadClipArt functions, and now, I end up with factories, virtual classes, one more argument to the loadClipArt Function! How can all this be seen as an improvement? I just moved the problem! The client still has to instantiate ShapeFactoryA or ShapeFactoryB!

And you would be right for most of those claims. I moved the problem elsewhere, which is precisely why it solves the problem.

Keeping Complexity (and Bob's ideas) under Control.

First, we consider the loadClipart as some client code of our ClipArt and Shape classes.

Without the abstract factory, we would have one function to load the Shapes for families A, B, and tomorrow, Bob will add C.

  • loadShapeA
  • loadShapeB
  • loadShapeC

Then Bob would like to load from JSON. So you will implement

  • loadJSonShapeA
  • loadJsonShapeB
  • loadJsonShapeC

then your customer extends your code and wand to load shape from a database:

  • loadDBShapeA
  • ....

So, client code will call the Shape creation from a text file, from JSON, from a database, maybe from the network. At EACH USE of those functions, he will have to decide for the family of Shape it will be using.

Now, if you use the Abstract Factory pattern, you instantiate ONCE the right factory for your application, and you provide it to the functions:

  • loadClipArt
  • loadJsonClipart
  • loadDBClipart

Of course, there is a price for that. You must provide your Factory to all the functions. But the choice of this factory in your main function, for instance, once and for all. Depending on what libraries are available on your system.

Benefits of the Abstract Factory Pattern

In terms of coupling, it is tremendous progress that reduces the dependencies between the involved classes. If you want to review why it is such a good thing, please read Part I of this series of articles.

Architecture evolution3.png

Looking at the diagram, it seems much more complex than what we had before. But it does a lot more.

The purple rounded rectangles show the various isolated libraries with their components. They only depend on the Factory interface and/or the Shape interface. They operate together but don't know each other.

This way, they can evolve without any consequences on the other modules. It becomes very easy to:

  • Add a shape family.
  • Add a new file format.
  • Add features to the ClipArt library.
  • All this without modifying the other modules.

It made the application much more scalable. Adding a new render library consists of implementing the corresponding shapes and a factory and instantiating it at one given location.

Another benefit: it made the code more testable. It is a crucial point.

If you want to test your ClipArt loading code, you could easily:

  • Implement some TestRectangle, TestCircle, and TestTriangle.
  • Implement a Factory for them and provide it to your testing code.
    Note that your test shapes implementation could be pretty close to what I provide in this article as examples.

In software architecture, the dependencies should go toward the most stable class. At the heart of our design, the Shape interface should have fewer reasons to change than other components. More on this below.

The cost of the Abstract Factory Pattern

The real drawback seems that you have to provide the factory to the objects that will instantiate shapes, and instead of calling a constructor, you call a virtual function that will call a constructor. It is a cost. But think about the alternatives:

  • Select at run time which kind of Shape to instantiate. It is a much higher cost, as you will clutter your code with if statements, and you will need to link with many libraries to render your Shapes (all those available for a given platform). And this is just the run time cost. You have to consider the code bloat, compile time, link time, debug time, etc... And the induced complexity will hurt your tests as well.
  • Select at compile time which kind of Shape to instantiate everywhere you need it, with #ifdefs. With sufficiently complex macros, you can probably make a version linking only with the appropriate library and have the maximum run time performance. But at the cost of some unreadable code, that will be very hard to maintain, test, and validate.
    The cost for code more robust, testable, and flexible will probably be more easily acceptable than the problems induced by those naive alternatives.

Of course, I already stated that the Shape Interface is at the center of our design, which means that it must be carefully designed as every other module depends on it. A change in the base shape class would potentially impact all the other modules.
It is a critical point of your design. The class with the most inbound dependencies relationships must be the one with fewer reasons to change!

Still, some different designs would allow us to achieve the same kind of flexibility. We will discuss one of them in Part III!

Wrapping Up

This article tries to do two things. The first one is explaining what could happen in a developer's head when making design and architecture decisions. We talked about dependencies, complexity, and requirement changes. The second one is solving a design problem that started with simple requirements but grew bigger. For that, we introduced our first Design Pattern: the Abstract Factory.
We saw the shortcomings of some naive solutions and how it elegantly solves the problem and offers a way to expand your software without adding any complexity. We can add different kinds of Shapes, with varying types of libraries for rendering, without changing most of the software code using the ClipArt library.
I see that as an axis for expansion. Once integrated into your design, it solves a family of problems (adding new kinds of Shapes without adding complexity).
We saw through simple schemas how to view the relationships and dependencies between classes simply and intuitively along the way. And how the propagation of changes goes in the opposite direction of the dependency arrow. And how it helps to tame complexity when the feature request list grows.

In the following article, we will see some alternative designs, and we will introduce another design pattern to handle Bob's new ideas.

Thank you for reading, and think before coding!

Guillaume Schmid
CTO at Kaer Labs.

If you find some errors in the article, you can send me a mail at guillaume_at_kaerlabs.com.

PS: Bob the Product Manager is purely fictional. Guys, keep bringing up new ideas!

Article Front image is the Crooked House in Poland by Krywy Domek
Image under Creative Commons 1.0: https://commons.wikimedia.org/wiki/File:Krzywy_Domek_Sopot_3.jpg
Picture of Bob courtesy of Pixabay:
https://pixabay.com/photos/dwarf-peace-spring-garden-mood-1336356/
Good Code XKCD Comic: https://xkcd.com/844/
I made all other illustrations.

Discover and read more posts from Guillaume Schmid
get started
post commentsBe the first to share your opinion
David Harris
6 months ago

From Simple Techniques To Design Patterns in C" is a valuable resource for programmers seeking to elevate their coding skills. The author’s comprehensive approach not only simplifies complex design patterns but also provides practical examples to reinforce learning. One standout example is the implementation of the Factory Method pattern, where the smart keyring becomes the central element for demonstrating the power of design patterns in real-world applications. By making the content relatable and engaging, this book succeeds in transforming intricate concepts into practical solutions. With its clear explanations and hands-on examples, it’s a must-have for developers looking to enhance their coding repertoire, making it as essential as a business card is for networking professionals.

Abbigael Jacob
a year ago

From simple techniques to design patterns in C++, my journey has been filled with exciting discoveries. As a user, I’ve delved into the world of coding, unlocking the power of custom avatars along the way. Starting with the basics, I learned about variables, loops, and functions, gradually building my understanding. As I progressed, I discovered the elegance of design patterns, utilizing them to create efficient and scalable solutions. With each step, I became more adept at harnessing the full potential of C++, leveraging custom avatars to personalize and enhance my creations. From humble beginnings to empowering designs, my C++ journey has been nothing short of transformative.

Freeda Fay
a year ago

Thanks for this post <a href=“https://www.google.com/”>Google</a>

Show more replies