Mary is a vegetarian. She only cooks and eats vegetarian food. John is carnivorous. He cooks and eats meat! If Mary wants to eat broccoli and cheese, she can learn how to cook broccoli and cheese. If she wants corn of the cob, she can learn how to cook corn on the cob. The same goes for John. If he wants to eat greasy hamburger, he can learn how to cook greasy hamburger. If he wants to eat fatty hotdog, he can learn how to cook fatty hotdog. Every time John and Mary want to eat something new, they can learn how to cook it. This requires that John and Mary to each have a very big head in order to learn all the recipes.
But wait, there are people out there called chefs! These are very special kinds of chefs catering only to vegetarians and carnivores. These chefs only know how to cook two dishes: one vegetarian dish and one meat dish. All John and Mary have to do is to know how to ask such a chef to cook their favorite dish. Mary will only order the vegetarian dish, while John will only order the meat dish!
How do we model the vegetarian, the carnivore, the chef, the two kinds of dishes the chef cooks, and how the customer orders the appropriate kind of dish from the chef?
To simplify the problem, let's treat food as String. (In a more sophisticated setting, we may want to model food as some interface with veggie and meat as sub-interface.)
Vegetarians and carnivores are basically the same animals. They have the basic ingredients such as salt and pepper to cook food. They differ in the kind of raw materials they stock to cook their foods and in the way they order food from a chef. Vegetarians
and Carnivores
can provide the materials to cook but do not know how to cook! In order to get any cooked meal, they have to ask a chef to cook for them. We model them as two concrete subclasses of an abstract class called AEater
. AEater
has two concrete methods, getSalt
and getPepper
, and an abstract method called order
, as shown in the table below.
Table 1: Top-level abstract definition
public abstract class AEater {
public String getSalt() {
return "salt";
}
public String getPepper() {
return "pepper";
}
/**
* Orders n portions of appropriate food from restaurant r.
*/
public abstract String order(IChef r, Integer n);
// NO CODE BODY!
} |
Table 2: Concrete implementations
public class Vegetarian extends AEater{
public String getBroccoli() {
return "broccoli";
}
public String getCorn() {
return "corn";
}
public String order(IChef c, Object n) {
// code to be discussed later;
}
}
|
public class Carnivore extends AEater{
public String getMeat() {
return "steak";
}
public String getChicken() {
return "cornish hen";
}
public String getDog() {
return "polish sausage";
}
public String order(IChef c, Object n) {
// code to be discussed later;
}
}
|
The chef is represented as an interface IChef
with two methods, one to cook a vegetarian dish and one to cook a meat dish, as shown in the table below.
Table 3: Top-level abstract definition
interface IChef {
String cookVeggie(Vegetarian h, Integer n);
String cookMeat(Carnivore h, Integer n);
}
|
Table 4: Concrete implementations
public class ChefWong implements IChef {
public static final ChefWong Singleton
= new ChefWong();
private ChefWong() {}
public String cookVeggie(
Vegetarian h, Integer n) {
return n + " portion(s) of " +
h.getCarrot() + ", " +
h.getSalt();
}
public String cookMeat(
Carnivore h, Integer n) {
return n + " portion(s) of " +
h.getMeat() + ", " +
h.getPepper();
}
} |
public class ChefZung implements IChef {
public static final ChefZung Singleton
= new ChefZung();
private ChefZung() {}
public String cookVeggie(
Vegetarian h, Integer n) {
return n + " portion(s) of " +
h.getCorn() + ", " +
h.getSalt();
}
public String cookMeat(
Carnivore h, Integer n) {
return n + " portion(s) of " +
h.getChicken() + ", " +
h.getPepper() + ", " +
h.getSalt();
}
} |
To order food from an IChef
, a Vegetarian
object simply calls cookVeggie
, passing itself as one of the parameters, while a Carnivore
object would call cookMeat
, passing itself as one of the parameters as well. The Vegetarian
and Carnivore
objects only deal with the IChef
object at the highest level of abstraction and do not care what the concrete IChef
is. The polymorphism machinery guarantees that the correct method in the concrete IChef
will be called and the appropriate kind of food will be returned to the AEater
caller The table below shows the code for Vegetarian
and Carnivore
, and sample client code using these classes.
Table 5: Concrete implementations
public class Vegetarian extends AEater {
// other methods elided
public String order(IChef c, int n) {
return c.cookVeggie(this, n);
}
}
|
public class Carnivore extends AEater {
// other methods elided
public String order(IChef c, int n) {
return c.cookMeat(this, n);
}
}
|
Table 6: Client code
public void party(AEater e, IChef c, int n) {
System.out.println(e.order(c, n));
}
// blah blah blah...
AEater John = new Carnivore();
AEater Mary = new Vegetarian();
party(Mary, ChefWong.Singleton, 2);
party(John,ChefZung.Singleton, 1);
|
The above design is an example of what is called the visitor pattern.
- The abstract class
AEater
and its concrete subclasses are called the hosts. The method public String order(IChef c, Object n)
is called the hook method. Each concrete subclasses of AEater
knows exactly to call the appropriate method on the IChef
parameter, but does know and need not how the IChef
concretely perforns its task. This allows an open-ended number of ways to cook the appropriate kinds of food.
- The chef interface
IChef
and all of its concrete implementations are called visitors. When an IChef
performs cookMeat/cookVeggie
, it knows that its host is a Carnivore
/Vegetarian
and can only call the methods of Carnivore
/Vegetarian
to cook a dish. Java static type checking will flag an error should there be a call on the host to getCarrot
in the method cookMeat
. This is makes the interaction between hosts (Vegetarian
and Carnivore
) and visitors (IChef
and all of its concrete implementations) much more robust.