Design Principles

SOLID

S = Single Responsibility Principle
O = Open-Closed Principle
L = Liskov Substitution Principle
I = Interface Segregation Principle
D = Dependency Inversion/Injection

Single Responsibility Principle

A class should only have a single responsibility.

SRP.cpp
  Journal journal("My Journal");
  journal.add("First Entry");
  journal.add("Second Entry");
  journal.add("Third Entry");

  // Use a separate class/entity for saving.
  // Saving journals is not a base responsibility of a journal.
  PersistenceManager().save(journal, "journal.txt");

Full source code SRP.cpp

Open-Closed Principle

Entities should be open for extension but closed for modification.

OCP.cpp
  Product apple{"Apple", Color::Green, Size::Small};
  Product tree{"Tree", Color::Green, Size::Large};
  Product house{"House", Color::Blue, Size::Large};

  ProductList all{apple, tree, house};

  BetterFilter bf;
  ColorSpecification green(Color::Green);

  auto green_things = bf.filter(all, green);
  for (auto& product : green_things)
    std::cout << product.name << " is green" << std::endl;

  SizeSpecification big(Size::Large);
  // green_and_big is a product specification
  AndSpecification<Product> green_and_big{big, green};

  auto green_big_things = bf.filter(all, green_and_big);
  for (auto& product : green_big_things)
    std::cout << product.name << " is green and big" << std::endl;

Full source code OCP.cpp

Liskov Substitution Principle

Objects should be replaceable with instances of their subtypes without altering program correctness.

LSP.cpp
  Rectangle r{5, 5};
  process(r);

  // Square (subtype of Rectangle) violates the Liskov Substitution Principle
  Square s{5};
  process(s);

Full source code LSP.cpp

Interface Segregation Principle

Many client-specific interfaces better than one general-purpose interface.

ISP.cpp
  Printer printer;
  Scanner scanner;
  Machine machine(printer, scanner);
  std::vector<Document> documents{Document(std::string("Hello")),
                                  Document(std::string("Hello"))};
  machine.print(documents);
  machine.scan(documents);

Full source code ISP.cpp

Dependency Inversion/Injection

Dependencies should be abstract rather than concrete.

Dependency Inversion Principle

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions. Example: reporting component should depend on a ConsoleLogger, but can depend on an ILogger.
  2. Abstractions should not depend upon details. Details should depend upon abstractions. In other words, dependencies on interfaces and supertypes is better than dependencies on concrete types.

Inversion of Control (IoC) – the actual process of creating abstractions and getting them to replace dependencies.

Dependency Injection – use of software frameworks to ensure that a component’s dependencies are satisfied.

BoostDI.cpp
  // without DI
  std::cout << "without DI\n";
  auto e1 = std::make_shared<Engine>();
  auto logger1 = std::make_shared<ConsoleLogger>();
  auto c1 = std::make_shared<Car>(e1, logger1);
  std::cout << *c1 << std::endl;

  // with DI
  std::cout << "with DI\n";
  using namespace boost;
  // whenever an ILogger is needed a ConsoleLogger instance will be created
  auto injector = di::make_injector(di::bind<ILogger>().to<ConsoleLogger>());
  // engine created with default constructor
  auto c = injector.create<std::shared_ptr<Car>>();

  std::cout << *c << std::endl;

Full source code BoostDI.cpp