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!!
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 onEspressoMachine
and an indirect (transitive) dependency onCoffeeMachine
. In the modifiedCoffeeApp
, 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, theCoffeeApp
becomes useless.