(ARTICLE): Finished the D, now just onto final touches and DRY.
I did push a lock file, I am tired of having to ignore it... and gitignore is not working for some reason.
This commit is contained in:
parent
467186f94e
commit
3d4b07c037
@ -401,7 +401,7 @@ adheres to the open/closed principle is easy to scale, expand and upgrade.
|
|||||||
|
|
||||||
## Liskov Substitution Principle
|
## Liskov Substitution Principle
|
||||||
|
|
||||||
The Liskov Substitution Principle states:
|
The [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) states:
|
||||||
|
|
||||||
>
|
>
|
||||||
> "Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for
|
> "Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for
|
||||||
@ -463,7 +463,7 @@ expectation for a rectangle is that the height and width will be set independent
|
|||||||
|
|
||||||
## Interface Segregation Principle
|
## Interface Segregation Principle
|
||||||
|
|
||||||
The interface segregation principle states:
|
The [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) states:
|
||||||
|
|
||||||
>
|
>
|
||||||
> "A client should never be forced to implement an interface that it doesn’t use, or clients
|
> "A client should never be forced to implement an interface that it doesn’t use, or clients
|
||||||
@ -569,6 +569,124 @@ public:
|
|||||||
|
|
||||||
## Dependency Inversion Principle
|
## Dependency Inversion Principle
|
||||||
|
|
||||||
|
The [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle) states:
|
||||||
|
|
||||||
|
>
|
||||||
|
> "Entities must depend on abstractions, not on concretions. It states that the high-level module
|
||||||
|
> must not depend on the low-level module, but they should depend on abstractions."
|
||||||
|
>
|
||||||
|
|
||||||
|
This means that a class should rely on abstractions (interfaces or abstract base classes) rather than
|
||||||
|
concrete implementations. The Dependency Inversion Principle (DIP) is a specific methodology for
|
||||||
|
creating loosely coupled modules. When implemented, the DIP allows high-level modules to exist
|
||||||
|
independently of the low-level modules.
|
||||||
|
|
||||||
|
A nice analogy I found states:
|
||||||
|
|
||||||
|
>
|
||||||
|
> *"In a software development team, developers depend on an abstract version control system (e.g., Git)
|
||||||
|
> to manage and track changes to the codebase. They don't depend on specific details of how Git works
|
||||||
|
> internally."* ~geeksforgeeks
|
||||||
|
>
|
||||||
|
|
||||||
|
### Code Example
|
||||||
|
|
||||||
|
To illustrate the DIP, we will use an example in which a high-level `Notifier` needs to send messages
|
||||||
|
to a low-level `SMSService`.
|
||||||
|
|
||||||
|
The initial implementation will work ***fine***; however, it violates the DIP. What will happen if the
|
||||||
|
requirements change and the `Notifier` needs to send messages to a new service, such as an `EmailService`?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Low-level Module (The Detail)
|
||||||
|
class SMSService {
|
||||||
|
public:
|
||||||
|
void sendSMS(const std::string &recipient, const std::string &message) const {
|
||||||
|
std::cout << "SMSService: Sending SMS to " << recipient << ": '" << message
|
||||||
|
<< "'\n";
|
||||||
|
}
|
||||||
|
// No abstraction/interface here
|
||||||
|
};
|
||||||
|
|
||||||
|
// High-level Module (The Logic)
|
||||||
|
class Notifier {
|
||||||
|
public:
|
||||||
|
Notifier(const std::string &recipient) : recipient(recipient) {}
|
||||||
|
|
||||||
|
// DIP VIOLATION: Notifier (high-level) directly depends on SMSService
|
||||||
|
// (low-level).
|
||||||
|
void send(const std::string &message) const {
|
||||||
|
SMSService smsService; // Direct, hard dependency on the concrete class
|
||||||
|
smsService.sendSMS(recipient, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string recipient;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
By updating the code to adhere to the DIP, we can allow our `Notifier` class to send messages to any
|
||||||
|
client through the use of an abstraction.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 1. Abstraction (The Contract/Interface)
|
||||||
|
class IMessageSender {
|
||||||
|
public:
|
||||||
|
virtual void sendMessage(const std::string &recipient,
|
||||||
|
const std::string &message) const = 0;
|
||||||
|
virtual ~IMessageSender() = default;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, we can create our clients; for this example, we only need two of them to demonstrate the
|
||||||
|
power of the **Dependency Inversion Principle**.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 2. Low-level Module depends on Abstraction (The Detail)
|
||||||
|
class SMSServiceDIP : public IMessageSender {
|
||||||
|
public:
|
||||||
|
void sendMessage(const std::string &recipient,
|
||||||
|
const std::string &message) const override {
|
||||||
|
std::cout << "SMSServiceDIP: Sending SMS to " << recipient << ": '"
|
||||||
|
<< message << "'\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// We can easily add a new service without touching the Notifier!
|
||||||
|
class EmailServiceDIP : public IMessageSender {
|
||||||
|
public:
|
||||||
|
void sendMessage(const std::string &recipient,
|
||||||
|
const std::string &message) const override {
|
||||||
|
std::cout << "EmailServiceDIP: Sending Email to " << recipient << ": '"
|
||||||
|
<< message << "'\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we can reimplement the `Notifier` class to depend only on the abstracted class and adhere
|
||||||
|
to the DIP.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 3. High-level Module depends on Abstraction (The Logic)
|
||||||
|
class Notifier {
|
||||||
|
public:
|
||||||
|
// **Dependency Injection:** The concrete service is passed in via the
|
||||||
|
// constructor. The Notifier only knows about the IMessageSender interface!
|
||||||
|
Notifier(IMessageSender *service, const std::string &recipient)
|
||||||
|
: sender_(service), recipient_(recipient) {}
|
||||||
|
|
||||||
|
void send(const std::string &message) const {
|
||||||
|
// The high-level module uses the abstraction (interface).
|
||||||
|
sender_->sendMessage(recipient_, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Stores a pointer to the generic interface
|
||||||
|
IMessageSender *sender_;
|
||||||
|
std::string recipient_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## SOLID Only For OOP?
|
## SOLID Only For OOP?
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user