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.
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");
Open-Closed Principle¶
Entities should be open for extension but closed for modification.
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;
Liskov Substitution Principle¶
Objects should be replaceable with instances of their subtypes without altering program correctness.
Rectangle r{5, 5};
process(r);
// Square (subtype of Rectangle) violates the Liskov Substitution Principle
Square s{5};
process(s);
Interface Segregation Principle¶
Many client-specific interfaces better than one general-purpose interface.
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);
Dependency Inversion/Injection¶
Dependencies should be abstract rather than concrete.
Dependency Inversion Principle
- 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.
- 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.
// 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;