Monday, December 9, 2024

Dependency Inversion Principle (DIP) Violation Example

A device controller system. We'll explore how the Dependency Inversion Principle can be applied.

// Abstraction
interface Device {
    void turnOn();
    void turnOff();
}

// Concrete Implementations
class Light implements Device {
    @Override
    public void turnOn() {
        // Logic to turn on the light
    }

    @Override
    public void turnOff() {
        // Logic to turn off the light
    }
}

class Fan implements Device {
    @Override
    public void turnOn() {
        // Logic to turn on the fan
    }

    @Override
    public void turnOff() {
        // Logic to turn off the fan
    }
}

// High-level module depending on abstraction
class DeviceController {
    private final Device device;

    public DeviceController(Device device) {
        this.device = device;
    }

    public void operateDevice() {
        // Common logic to operate the device
        device.turnOn();
        // Additional logic if needed
        device.turnOff();
    }
}


In this example:

The Device interface represents the abstraction, defining common methods like turnOn and turnOff.
The Light and Fan classes are concrete implementations of the Device interface.

The DeviceController class is the high-level module that operates a device. It depends on the Device abstraction.

By adhering to the Dependency Inversion Principle, the DeviceController class is not concerned with the specific details of how each device turns on or off. It relies on the abstraction, allowing for flexibility. If a new type of device is introduced, you can create a new class implementing the Device interface without modifying the DeviceController class. This promotes a more modular and maintainable codebase. 

How break DIP in above code

To break the Dependency Inversion Principle (DIP) in the given code, you would modify it so that the **high-level module (DeviceController) depends on concrete implementations (Light, Fan) instead of the abstraction (Device). The DIP ensures that high-level modules depend on abstractions, not on concrete implementations.


Currently, the DeviceController class depends on the Device interface (abstraction). This means it can work with any Device implementation, such as Light or Fan, without knowing their details. This adheres to the DIP because:
  • High-level module: DeviceController depends on the abstraction Device, not the concrete classes Light or Fan.

Breaking DIP: High-level module depends on concrete classes

To break the DIP:

  1. Remove the dependency on the Device interface.
  2. Make DeviceController directly depend on the specific implementations, like Light or Fan.

Example:

class DeviceController {
    private final Light light; // Direct dependency on a concrete implementation

    public DeviceController() {
        this.light = new Light(); // Instantiating a concrete class inside the controller
    }

    public void operateDevice() {
        // Directly calling methods on the Light class
        light.turnOn();
        // Additional logic if needed
        light.turnOff();
    }
}

What's wrong here:

  1. DeviceController is tightly coupled to the Light class.
    • You cannot reuse DeviceController with another device (e.g., Fan) without modifying its code.
  2. Violates the Open/Closed Principle:
    • To add support for another Device like Fan, you must modify DeviceController.
  3. Reduces flexibility:
    • No way to dynamically pass different Device implementations at runtime.

Consequences of Breaking DIP

  1. Reduced Testability:
    • You can't substitute a mock Device for testing purposes because DeviceController is tied to specific implementations.
  2. Tight Coupling:
    • DeviceController now relies on concrete classes, which makes it harder to maintain and extend.
  3. Violated Dependency Injection:
    • Dependency injection is no longer possible since DeviceController instantiates the Light object directly.

Restoring DIP

To fix this and restore adherence to DIP, you would:

  1. Depend on the abstraction (Device).
  2. Use dependency injection to pass the implementation (Light or Fan) into DeviceController.

Example:

class DeviceController {
    private final Device device;

    public DeviceController(Device device) {
        this.device = device; // Dependency injection
    }

    public void operateDevice() {
        device.turnOn();
        device.turnOff();
    }
}

Now, DeviceController is flexible and adheres to DIP.


No comments:

Post a Comment

LeetCode C++ Cheat Sheet June

🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...