Reputation: 23
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()
}
}
}
Upvotes: 2
Views: 707
Reputation: 59303
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:
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.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.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.Upvotes: 1
Reputation: 5294
Fundamentally the example given seems a bit backward. A pizza can't bake itself or apologize to a client. If you rewrite your classes such that there is a server (waiter) who has a generic serve method then your problem should be solved. A server that implements Canadian will apologize for serving a burnt pizza, the wrong pizza, etc., but the apology will be invoked in the serve method which will take the generic pizza argument.
interface Pizza {
String name();
}
interface Canadian {
void apologize(String message);
}
interface Server {
void serve(Pizza pizza, Client client);
}
class CanadianServer implements Canadian, Server {
public void serve(Pizza pizza, Client client) {
if (!client.orderContains(pizza.name()) {
apologize("Sorry I brought you the wrong pizza");
pizza = change(pizza, PizzaFactory.getPizzaFor(client));
}
// serve Logic here
}
public void apologize(String message) {
// Say apology
}
}
Upvotes: 0
Reputation: 95
I think in your case the more applicable is factory method pattern:
interface Pizza{ }
class CanadianPizza implements Pizza{ }
class OtherPizza implements Pizza{ }
abstract class PizzaCreator {
public abstract Pizza factoryMethod();
}
class CanadianPizzCreator extends PizzaCreator {
@Override
public Pizza factoryMethod() { return new CanadianPizza(); }
}
class OtherPizzaCreator extends PizzaCreator {
@Override
public Pizza factoryMethod() { return new OtherPizza(); }
}
public class FactoryMethodExample {
public static void main(String[] args) {
// an array of creators
Creator[] creators = {new CanadianPizzaCreator(), new OtherPizzaCreator()};
// iterate over creators and create products
for (Creator creator: creators) {
Pizza pizza = creator.factoryMethod();
System.out.printf("Created {%s}\n", pizza.getClass());
}
}
}
In my opinion, your approach is not quite right. I recommend looking at other design pattern
Upvotes: 0