Principles:

b

Single Responsibility

b

Open/Closed

b

Liskov Substitution

b

Interface Segregation

b

Dependancy Inversion

Output:

Journal: Dear Diary
1: I ate a bug
2: I cried today

Single Responsibility:

#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Journal
{
   string title;
   vector<string> entries;
   explicit Journal(const string& title) : title{ title }
   {
   }
   void add(const string& entry)
   {
      static int count = 1;
      entries.push_back(to_string(count++) + “: ” + entry);
   }
};
struct OutputManager
{
   static void print(const Journal& j)
   {
      cout << “Journal: ” << j.title << endl;
      for (auto& s : j.entries)
      cout << s << endl;
   }
};
int main()
{
   Journal journal{ “Dear Diary” }; // Journal only adds entries. It has a single responsibility.
   journal.add(“I ate a bug”);
   journal.add(“I cried today”);
   OutputManager om; // OutputManager only prints the Journal. It has a single responsibility.
   om.print(journal);
   return 0;
}

Output:

expected area = 25, got 25

Open/Closed:

#include <string>
#include <iostream>
using namespace std;
class Shape // this is closed to modification but is open to extension
{
public:
   virtual int area() = 0;
};
class Rectangle : public Shape // this extends the Shape functionality
{
protected:
   int width, height;
public:
   Rectangle(int width, int height) : width{ width }, height{ height }
   {
   }
   int get_width() { return width; }
   int get_height() { return height; }
   int area() { return width * height; }
};
void check_area(Rectangle& r)
{
   int w = r.get_width();
   int h = r.get_height();
   std::cout << “expected area = ” << (w * h) << “, got ” << r.area() << std::endl;
}
int main()
{
   Rectangle r{ 5, 5 };
   check_area(r); // Child Rectangle extends the parent’s pure virtual area function
   return 0;
}

Output:

expected area = 50, got 50
expected area = 50, got 100

Liskov Substitution:

#include <iostream>

class Rectangle
{
protected:
   int width, height;

public:
   Rectangle(const int width, const int height) : width{ width }, height{ height }
   {
   }

   int get_width() const { return width; }
   virtual void set_width(const int width) { this->width = width; }

   int get_height() const { return height; }
   virtual void set_height(const int height) { this->height = height; }

   int area() const { return width * height; }
};

 class Square : public Rectangle
{
public:
   Square(int size): Rectangle(size, size)
   {
   }

   void set_width(const int width) override
   {
      this->width = height = width;
   }

   void set_height(const int height) override
   {
      this->height = width = height;
   }
};

void check_area(Rectangle& r) // Liskov Substitution principle
{
   int w = r.get_width();
   r.set_height(10);

   std::cout << “expected area = ” << (w * 10) << “, got ” << r.area() << std::endl;
}

 int main()
{
   Rectangle r{ 5, 5 };
   check_area(r);

   Square s{ 5 };
   check_area(s); // expects a reference to base class Rectangle but can use derived class Square

   return 0;
}

Output:

printing
scanning

Interface Segregation:

#include <iostream>
#include <string>
using namespace std;
struct IPrinter
{
   virtual void print() = 0;
};
struct IScanner
{
   virtual void scan() = 0;
};
struct Printer : IPrinter
{
   void print() override { cout << “printing” << endl; }
};
struct Scanner : IScanner
{
   void scan() override { cout << “scanning” << endl; }
};
struct IMachine : IPrinter, IScanner
{
};
struct Machine : IMachine
{
   bool isOn = false;
   IPrinter& printer; // The printer interface is segregated from the machine interface
   IScanner& scanner; // The scanner interface is segregated from the machine interface
   Machine(IPrinter& printer, IScanner& scanner) : printer{ printer }, scanner{ scanner }
   {
   }
   void print() override
   {
      printer.print();
   }
   void scan() override
   {
      scanner.scan();
   }
};
int main()
{
   Printer printer;
   Scanner scanner;
   Machine machine(printer, scanner);
   machine.print();
   machine.scan();
   return 0;
}

Output:

John has a child called Chris
John has a child called Matt

Dependancy Inversion:

#include <tuple>

using namespace std;

enum class Relationship
{
   parent,
   child,
   sibling
};

struct Person
{
   string name;
};

struct RelationshipBrowser // abstraction
{
   virtual vector<Person> find_all_children_of(const string& name) = 0;
};

struct Relationships : RelationshipBrowser // low-level module
{
   vector<tuple<Person, Relationship, Person>> relations;

   void add_parent_and_child(const Person& parent, const Person& child)
   {
      relations.push_back({parent, Relationship::parent, child});
      relations.push_back({child, Relationship::child, parent});
   }

   vector<Person> find_all_children_of(const string& name) override
   {
      vector<Person> result;

      for (const auto& i : relations)
      {
         if (get<0>(i).name == name && get<1>(i) == Relationship::parent)
         result.push_back(get<2>(i));
      }

      return result;
   }
};

struct Research // high-level module
{
   Research(RelationshipBrowser& browser)
   {
      for (auto& child : browser.find_all_children_of(“John”))
         cout << “John has a child called ” << child.name << endl;
   }
};

int main()
{
   Person parent{“John”};
   Person child1{“Chris”};
   Person child2{“Matt”};

   Relationships relationships;
   relationships.add_parent_and_child(parent, child1);
   relationships.add_parent_and_child(parent, child2);

   Research research(relationships); // High-level modules should not depend on low-level modules.
   // Both should depend on abstractions.
   // Or, abstractions should not depend on details. Details should depend on abstractions.
   return 0;
}