Search code examples
javasolid-principlesdependency-inversion

Is this still follow Dependency Inversion Principle when implement multiple interface?


sorry for the long question and also my English.

I'm reading an article about DIP. I will summarize the code in here.

interface CoffeeMachine() {
    void brewFilterCoffee();
}

interface EspressoMachine() {
    void brewEspressoCoffee();
}

They create two different CoffeeMachine. BasicCoffeeMachine and PremiumCoffeeMachine. They both have the same feature is brewFilterCoffee(); so they put it on the CoffeeMachine interface

class BasicCoffeeMachine implements CoffeeMachine {
    @Override
    void brewFilterCoffee() {
        System.out.println("brewing filter coffee...");
    }
}

// this one can make Espresso
class PremiumCoffeeMachine implements CoffeeMachine, EspressoMachine {
    @Override
    void brewFilterCoffee() {
        System.out.println("brewing filter coffee but in premium way...");
    }

    @Override
    void brewEspressoCoffee() {
        System.out.println("brewing espresso coffee...");
    }
}

When they create CoffeeApp, it accepts CoffeeMachine interface in the constructor and uses it to prepareCoffee()

class CoffeeApp {
    CoffeeMachine machine;
    
    public CoffeeApp(CoffeeMachine machine) {
        this.machine = machine;
    }
    
    public void prepareCoffee() {
        machine.brewFilterCoffee();
    }
}

In the Main class.

class Main {
    public static void main(String[] args) {
        PremiumCoffeeMachine premiumCoffeeMachine = new PremiumCoffeeMachine();
        CoffeeApp app = new CoffeeApp(premiumCoffeeMachine);

        app.brewFilterCoffee();
    }
} 

I left confused here because they didn't mention how they use brewEspressoCoffee() in CoffeeApp. So I go ahead and modify CoffeeApp like this:

class CoffeeApp {
    public void prepareFilterCoffee(CoffeeMachine machine) {
        machine.brewFilterCoffee();
    }
    
    public void prepareEspressoCoffee(EspressoMachine machine) {
        machine.brewEspressoCoffee();
    }
}

In the Main class, if I want to brewEspressoCoffee(), I just create an instance that implements EspressoMachine

class Main {
    public static void main(String[] args) {
        PremiumCoffeeMachine premiumCoffeeMachine = new PremiumCoffeeMachine();
        CoffeeApp app = new CoffeeApp();

        app.brewEspressoCoffee(premiumCoffeeMachine);
    }
} 

Is this still the following DIP? And is there any better way to approach rather than this example? Any example would be appreciated.

Thank you!!


Solution

  • I think you've captured the essence of the DIP, which is that you can always insert an interface to invert the direction of a dependency.

    Beyond just following the DIP, there is also the principle of Information Hiding to consider here. We often think of IH as applied to data, but it applies to dependencies as well.

    In the original CoffeeApp, the client (customer) has no dependency on EspressoMachine and an indirect (transitive) dependency on CoffeeMachine. In the modified CoffeeApp, the client has direct dependencies on both Machine interfaces.

    These dependencies are on abstractions, so the DIP is satisfied; but it begs the question, if CoffeeApp exposes its dependencies to its clients, then what is its purpose? Clients can invoke those dependencies directly. By passing on its dependencies, the CoffeeApp becomes useless.