If you say DI stands for Dependency Injection and IOC stands for Inversion of Control, you are correct. But remember, ‘There is a difference between knowing the name of something and truly understanding it.. So what is exactly these two do and how it is used.
Dependency Injection (DI) is a design pattern where one object, often referred to as a dependency injector or provider, supplies or injects the dependencies required by another object. This approach facilitates the decoupling of components within a software system, promoting modularity, flexibility, and ease of testing. By externalizing the management of dependencies, DI enables more maintainable and scalable codebases, where components can be easily replaced or configured without affecting the overall system functionality.
<?php
class PaymentGateWay
{
protected $gateway;
public function __construct(ChargeBee $chargeBee)
{
$this->gateway = $chargeBee;
}
public function charge()
{
$this->gateway->payNow();
}
}
class ChargeBee
{
protected $subscription;
public function __construct(ChargeBeeSubscription $subscription)
{
$this->subscription = $subscription;
}
public function removeSubscription()
{
$this->subscription->cancelSubscription();
}
public function payNow()
{
//do stuff
}
}
class ChargeBeeSubscription
{
public function createSubscription()
{
//do stuff
}
public function cancelSubscription()
{
//do stuff
}
}
$subscription = new ChargeBeeSubscription();
$chargeBee = new ChargeBee($subscription);
$gateway = new PaymentGateWay($chargeBee);
In the provided code snippet, there is a dependency relationship between the ChargeBee
class and the ChargeBeeSubscription
class, as well as between the PaymentGateWay
class and the ChargeBee
class. This means that in order to instantiate a ChargeBee
object, a ChargeBeeSubscription
object must be provided, and similarly, in order to instantiate a PaymentGateWay
object, a ChargeBee
object must be provided. By injecting dependencies through constructors, the code adheres to the dependency injection principle.
You may think, hey here ChargeBee is tightly Coupled to the PaymentGateWay. what if I want to change my payment process to Stripe. Indeed, you’ve spotted a crucial concern. When classes like ChargeBee
are tightly coupled to classes like PaymentGateWay
, changing the payment processing provider to something like Stripe can become a daunting task, as it would require modifications to multiple parts of the codebase. This is where Inversion of Control (IoC) comes into play.
Inversion of Control (IoC) -> Defining once at the Framework or class level, how to implement specific features or code paths instead of multiple times in the code.
The control over object instantiation and management is delegated to an IoC container, which acts as a centralized hub for managing dependencies. Instead of instantiating objects directly within the code, classes declare their dependencies, and the IoC container is responsible for providing these dependencies at runtime
Interfaces play a crucial role in Inversion of Control and design patterns by serving as contracts. Any class that utilizes an interface must implement all the methods declared within that interface. This ensures adherence to a standardized set of behaviors, promoting modularity and flexibility within the codebase.
<?php
interface PaymentService {
public function processPayment();
}
class PaymentGateway {
private $paymentService;
public function __construct(PaymentService $paymentService) {
$this->paymentService = $paymentService;
}
public function processPayment() {
$this->paymentService->processPayment();
}
}
// StripePaymentService.php
class StripePaymentService implements PaymentService {
public function processPayment() {
echo "Processing payment with Stripe\n";
}
}
// ChargeBeePaymentService.php
class ChargeBeePaymentService implements PaymentService {
public function processPayment() {
echo "Processing payment with ChargeBee\n";
}
}
//Ideally, these classes would be resolved at the framework level based on the config values.
$stripePaymentService = new StripePaymentService();
$chargeBeePaymentService = new ChargeBeePaymentService();
// Configure PaymentGateway with StripePaymentService
$paymentGatewayWithStripe = new PaymentGateway($stripePaymentService);
$paymentGatewayWithStripe->processPayment(); // Output: Processing payment with Stripe
// Configure PaymentGateway with ChargeBeePaymentService
$paymentGatewayWithChargeBee = new PaymentGateway($chargeBeePaymentService);
$paymentGatewayWithChargeBee->processPayment();
With this setup, we can easily switch between different payment processing services (Stripe, ChargeBee, etc.) by simply providing the appropriate implementation of the PaymentService
interface to the PaymentGateway
constructor. This demonstrates how IoC can help decouple components and make the codebase more flexible and maintainable.
If we consider the provided code, switching from using the ChargeBee service to Stripe would only require changes in the sections where objects are instantiated. This flexibility allows for seamless runtime adjustments. Moreover, incorporating additional services that adhere to the PaymentService interface is straightforward, as the rest of the code remains unaffected. This approach streamlines maintenance and facilitates runtime modifications with minimal impact on existing functionality.