For concrete classes that implement multiple interfaces, how can clients to that class follow Dependency Inversion?

Hesh :

My issue is that I have a concrete implementation which implements two different interfaces. In the spirit of Dependency Inversion Principle, I would like clients to this class to only depend on an interface and not the concrete implementation. However, as this class uniquely implements these two interfaces; it seems to me that all clients will need to program to this concrete implementation.

Take the example below: I have a Restaurant class which for the most part only needs to interact with implementations of the Pizza interface. However, the CanadianPizza class also implements the Canadian interface, with the intention that every time someone serves this Pizza they must apologize, it seems like my Restaurant class would need to have some coupling with the concrete impl (see below).

Looking for ways to avoid depending on concrete implementations with classes that are a unique composition of multiple interfaces.

interface Pizza {
    void bake()
    void addToppings()
    void rollDough()
}

interface Canadian {
    void apologize()
}

class CanadianPizza implements Pizza, Canadian {
    @Override
    void bake() { ... } 
    @Override
    void apologize { ... }
}

class Restaurant {

    private final Pizza mPizza;

    constructor(Pizza pizza) {
        mPizza = pizza; 
    }

    void serveFood() {
        mPizza.rollDough()
        mPizza.addToppings()
        doSomethingWithPizza()
        // But I also need to know if my Pizza is Canadian
        if (mPizza instanceof CanadianPizza) {
            ((CanadianPizza) mPizza).apologize()
        }
    }
}
Matt Timmermans :

The business rule "you have to apologize when you serve a Canadian pizza", should be implemented in the CanadianPizza class, because that's where we implement the rules for Canadian pizzas.

But it's the restaurant server that needs to apologize, so some additional capabilities will be required in the restaurant to support this, and the connection between the Restaurant and those capabilities should be provided in the Pizza interface, because the purpose of that interface is to capture the things that restaurants need from pizzas.

There is no place for a Canadian interface at all.

Here are some reasonable choices:

  • While serving, the restaurant checks Pizza.requiresApology(), which returns true for Canadian pizzas. This is not great, because the designer of the restaurant class needs to anticipate all the stuff that might need to happen after a delivery.
  • While serving, the restaurant calls Pizza.onServed(Restaurant, Customer), during which a Canadian pizza will call Restaurant.apologize(Customer). This provides the opportunity for other kinds of pizza to add knives and forks, or do things you don't anticipate. There is still a little problem, though, in that there needs to be a rule that the restaurant has to call this method while serving, and rules like that tend to be occasionally forgotten in practice.
  • Pizzas can be responsible for serving themselves. The restaurant just calls Pizza.serve(Restaurant, Customer), and Canadian pizzas call customer.take(this); restaurant.apologize(customer). This provides even more flexibility for pizzas to define their behaviour, and doesn't impose any special rules that callers have to remember.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=154076&siteId=1