9

I have a class hierarchy like this Beverage -> Coffee-> Latte. Where Beverage is the abstract superclass being extended by Coffee. Coffee class then adds some behavior but is also abstract. Latte extends Coffee class and is a concrete class. I have used inheritance to add behaviors here. And inheritance do have drawbacks like the visibility of superclass methods, making code fragile, the code is tightly coupled. So, programming principles dictate Composition should be preferred over Inheritance. But in this case inheritance feels so natural as Latte is a type of Coffee and Coffee is a type of Beverage that using composition to add behavior feels wrong in spite of its benefits. So the question here is Should Intuition override Design principles?

Beverage:

public abstract class Beverage {

    private final String name;
    private final double price;

    Beverage(String name, double price){
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }
    public double getPrice() {
        return price;
    }

    public abstract void make();
    }

Coffee:

public abstract class Coffee extends Beverage {

    public Coffee(String name, double price) {
        super(name, price);
    }


    public final void make(){
        grindBeans();
        takeShot();
        frothMilk();
        addCondiments();
    }

    public void grindBeans(){
        System.out.println("Grinding Beans...");
    }

    public void takeShot(){
        System.out.println("Taking Shot....");
    }

    public abstract void frothMilk();
    public abstract void addCondiments();


}

Latte:

public class Latte extends Coffee {

    public Latte() {
        super("Latte", 4.0);
    }

    @Override
    public void frothMilk() {
        System.out.println("Frothing milk to create micro foam");

    }

    @Override
    public void addCondiments() {
        // TODO Auto-generated method stub

    }

}

EDIT: Adding Sugar to existing structure. Only new code is shown.

public abstract class Beverage {

private Sugar sugar;

public Sugar getSugar() {
    return sugar;
}

public void setSugar(Sugar sugar) {
    this.sugar = sugar;
}

}

Coffee:

public abstract class Coffee extends Beverage {

public final void make(){
    grindBeans();
    takeShot();
    frothMilk();
    addSugar();
    addCondiments();
}

public void addSugar(){
    Sugar sugar = super.getSugar();
    if(!(sugar instanceof NoSugar)){
        System.out.println("adding " + sugar.getTeaspoon() + " teaspoon sugar");
    }
}
Meena Chaudhary
  • 9,909
  • 16
  • 60
  • 94
  • possible duplicate of [Prefer composition over inheritance?](http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – jaco0646 Sep 21 '15 at 14:15
  • This question asks for opinion about intuition vs. design principles. – lit Oct 06 '15 at 18:31
  • To _prefer_ composition does not mean to reject inheritance. It means choose composition more often than you choose inheritance. I prefer walking to work, but I choose to drive when driving makes more sense. This is not intuition. It is evaluation and analysis. – jaco0646 Oct 23 '18 at 18:27

5 Answers5

6

While composition has many benefits over inheritance, there's nothing wrong with using inheritance where it feels natural (i.e. in a truly is-a relationship). If it's natural, go ahead and use it.

leeor
  • 17,041
  • 6
  • 34
  • 60
3

Composition means A has a B, and inheritance means A is kind of B. In your case you're 100% right - inheritance should be used: latee is a cofee and a cofee is a beverage.

Why do you consider it to be fragile? E.g., latee should have all the properties of cofee, but can implement them differently. Nothing fragile here - it's a polymorphism. If you want to restrict overriding of parent's methods - mark them final.

As an example of composition - there are Car and Wheel. Car has a wheel. Car needs wheels for working, but they're completely different objects. Car can be opened, closed, started, etc.. - wheel can't. Wheel can revolute and deflate. Car - can't.

P.S.: Oh, I think I got what you mean by "fragile". Here is an article on it http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should-you-choose-.html For me this "composition instead of inheritance" thing still looks like an OOP hack (especially, the example in the article: apple IS A fruit, no way around it :)) Whenever I will see this hack in use, I would probably think of an API designer to be improvident.

smyatkin_max
  • 365
  • 2
  • 10
  • I do agree with you but in spite of it being natural I do think Inheritance is not good for complex applications. for e.g Marcin Szymczak answer above do make sense. That adding new objects will be hard to maintain. So I tried to add a `Sugar` object using existing inheritance structure(see my edit to the post). But that way my classes are always open for modification which is again a violation of design principle. What do you say? – Meena Chaudhary Oct 04 '15 at 10:58
1

I think in the book head first design patterns the example they use is that the latte class can have several configurations. By using composition you can create custom classes at runtime rather than during design

You can only extend from one class as well which may be a limiting factor depending on what you are doing.

pwilcox83
  • 51
  • 7
1

Yes, at first sight inheritance here seems all right going with the argument that wherever its naturally so it makes sense to have inheritance. Coffee is a beverage and latte is a coffee so inheritance is okay.

Though it makes sense, what if we have more types of coffees - cappuccino, frappe etc. Then do we make more sub-types? I think then we can use a decorator pattern where Latte extends coffee and will itself be a coffee and so on. In this case we will make use of composition and inheritance both together. So, twisting the argument a bit, in this case we need both Composition and Inheritance and not either/or.

Having said that( and if you bought what I said above), what if we keep on decorating this Latte say by having variants American Latte and French Latte. Then which is our base class for a decorator? Is it Coffee or Latte? If we choose Coffee then American Latte is composed of Latte which is composed of Coffee. Composition is pulling it all together. If, however, we say its Latte then we make another base decorator component i.e. Latte. Then we have 2 levels of decorators - one centered around coffee and another (1 level below) centered around Latte which makes it too confusing. So, it makes more sense to have Coffee as the abstract decorator and use composition to bring in Latte and then French/American latte. Then, composition has an edge over inheritance here.

Dhruv Rai Puri
  • 1,335
  • 11
  • 20
1

If you are developing simple system where you have four subtypes of coffee then you should stay with inheritance.


Solution with inheritance have its problems. They will be visible especially when amount of coffee subtypes will increase.

First problem is with unimplemented methods. You will most likely have some methods which will just have empty implementation.

public class Espresso extends Coffee {

    public Espresso() {
        super("Espresso", 4.0);
    }

    @Override
    public void frothMilk() {
        // well I don't really need that method, so I will just write a comment

    }

    @Override
    public void addCondiments() {
        // that one is also unnecessary, does it mean that my inheritance tree is wrong?

    }

}

Second problem will occur with exponential explosion of parameters in your coffees. If you decide to have Latte with sugar you might end up with special class for this Latte, LatteWithSugar, which will also create new *.WithSugar for each already existing class. The same with each special modification of class. Please note that amount of classes increases exponentially. If you have 8 kinds of coffee adding one parameter will make 16 classes which is suspicious.


It is a matter of scale.

For small app it might be overengineering. For big coffee shop application it will save you hours of maintenance.

Marcin Szymczak
  • 11,199
  • 5
  • 55
  • 63
  • I do somewhat agree with you, but I am not completely convinced. So I tried making it work with current structure by adding `Sugar` by composition in `Beverage`. And then adding `addSugar()` to `make()` method in `Coffee` and `Tea`. It worked without having to create any extra classes, but downside is my classes are open for modification. Rest I think is good. – Meena Chaudhary Oct 04 '15 at 10:47