(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:
Hayden Hargreaves 2025-11-09 22:46:43 -07:00
parent 467186f94e
commit 3d4b07c037
2 changed files with 120 additions and 2 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -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 doesnt use, or clients > "A client should never be forced to implement an interface that it doesnt 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?