The SOLID principles help us design clean, maintainable, and scalable software. Here's how we can understand them using the example of musical instruments. Each principle has its own explanation, example, and link to detailed code or diagrams.
Definition: A class should have only one reason to change. It should focus on one responsibility.
An instrument should only handle its own behavior, like playing notes or tuning, and not unrelated tasks such as ticket printing.
class Guitar {
public:
void play() {
std::cout << "strumming the guitar..." << std::endl;
};
}
class TicketPrinter {
public:
void printTicket() {
std::cout << "printing concert ticket..." << std::endl;
}
}
Definition: A class should be open to extension but closed to modification.
Adding a new instrument should not require changing the existing code. Use inheritance or interfaces to achieve this.
class Instrument {
public:
virtual void playSound();
};
class Guitar : public Instrument {
public:
void playSound() override {
std::cout << "strumming the guitar..." << std::endl;
}
};
class Piano : public Instrument {
public:
void playSound() override {
std::cout << "playing the piano..." << std::endl;
}
};
Definition: Objects of a parent class should be replaceable with objects of a child class without affecting the behavior.
An orchestra can play any instrument as long as it fits into the category of "Instrument."
class Orchestra {
public:
void playInstrument(Instrument * instrument) {
instrument->playSound();
}
};
Orchestra orchestra;
Instrument * guitar = new Guitar();
orchestra.playInstrument(guitar);
Definition: A class should not be forced to implement interfaces it does not use.
Separate interfaces ensure that an instrument only implements methods relevant to its behavior.
class StringInstrument {
public:
virtual void tuneStrings();
};
class PercussionInstrument {
public:
virtual void hitDrum();
};
class Guitar : public StringInstrument {
public:
void tuneStrings() override {
std::cout << "tuning the guitar strings..." << std::endl;
}
};
Definition: High-level modules should depend on abstractions, not details.
The orchestra should not know the details of how each instrument works. It only depends on the abstraction (Instrument).
class Instrument {
public:
virtual void playSound();
};
class Orchestra {
public:
void play(Instrument * instrument) {
instrument->playSound();
}
};
class Piano : public Instrument {
public:
void playSound() override {
std::cout << "playing the piano..." << std::endl;
}
};
Instrument * piano = new Piano();
Orchestra orchestra;
orchestra.play(piano);