Skip to content

Solid_principles

SOLID is a set of five design principles that make software more understandable, flexible, and maintainable.

A class should have only one reason to change.

// Bad: Multiple responsibilities
class User {
public:
void setName(const std::string& name) { name_ = name; }
void saveToDatabase() { /* Save to DB */ }
void sendEmail() { /* Send email */ }
};
// Good: Separate responsibilities
class User {
private:
std::string name;
public:
void setName(const std::string& n) { name = n; }
std::string getName() const { return name; }
};
class UserRepository {
void save(const User& user) { /* Save to DB */ }
};
class EmailService {
void send(const User& user, const std::string& msg) { /* Send */ }
};

Software entities should be open for extension but closed for modification.

// Bad: Need to modify for new shapes
class AreaCalculator {
public:
double area(const std::string& shape, const std::vector<double>& params) {
if (shape == "circle") {
return 3.14 * params[0] * params[0];
} else if (shape == "rectangle") {
return params[0] * params[1];
}
// Need to add new case for each shape!
return 0;
}
};
// Good: Open for extension, closed for modification
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.14 * radius * radius; }
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override { return width * height; }
};
double totalArea(const std::vector<Shape*>& shapes) {
double total = 0;
for (const auto* s : shapes) {
total += s->area(); // Works for any shape!
}
return total;
}

Objects of a superclass should be replaceable with objects of its subclasses.

// Bad: Square inherits from Rectangle but changes behavior
class Rectangle {
protected:
double width, height;
public:
void setWidth(double w) { width = w; }
void setHeight(double h) { height = h; }
};
class Square : public Rectangle {
public:
void setWidth(double w) {
width = height = w; // Breaks expectations!
}
void setHeight(double h) {
width = height = h; // Breaks expectations!
}
};
void process(Rectangle& r) {
r.setWidth(5);
r.setHeight(10);
// Expected: width=5, height=10
// With Square: width=10, height=10 - BROKEN!
}

Prefer small, specific interfaces over large, general ones.

// Bad: Fat interface
class Worker {
public:
virtual void work() = 0;
virtual void eat() = 0;
virtual void sleep() = 0;
};
class Robot : public Worker {
public:
void work() override { /* Work */ }
void eat() override { /* Robot doesn't eat! */ }
void sleep() override { /* Robot doesn't sleep! */ }
};
// Good: Segregated interfaces
class Workable {
public:
virtual void work() = 0;
};
class Eatable {
public:
virtual void eat() = 0;
};
class Sleepable {
public:
virtual void sleep() = 0;
};
class Human : public Workable, public Eatable, public Sleepable {
public:
void work() override { /* Work */ }
void eat() override { /* Eat */ }
void sleep() override { /* Sleep */ }
};
class Robot : public Workable {
public:
void work() override { /* Work */ }
};

Depend on abstractions, not concrete implementations.

// Bad: Depends on concrete class
class MySQLDatabase {
public:
void connect() { /* Connect to MySQL */ }
};
class UserService {
private:
MySQLDatabase db; // Tightly coupled
public:
void save() { db.connect(); }
};
// Good: Depend on abstraction
class Database {
public:
virtual void connect() = 0;
virtual ~Database() = default;
};
class MySQLDatabase : public Database {
public:
void connect() override { /* Connect to MySQL */ }
};
class PostgreSQLDatabase : public Database {
public:
void connect() override { /* Connect to PostgreSQL */ }
};
class UserService {
private:
Database& db; // Depends on abstraction
public:
UserService(Database& database) : db(database) {}
void save() { db.connect(); }
};
  • S: One class, one responsibility
  • O: Open for extension, closed for modification
  • L: Subclasses should be substitutable for base classes
  • I: Many small interfaces > one large interface
  • D: Depend on abstractions, not concretions

Now let’s learn about build systems - essential for any C++ project.

Next Chapter: 10_build_systems/49_cmake.md - CMake Mastery