Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Implementations can be replaced at run-time for object composition(interface inheritance)

Tags:

java

I have came across the following point the advantage of object composition over class inheritance. But I often see the following sentence in many articles

In object composition, functionality is acquired dynamically at run-time by objects collecting references to other objects. The advantage of this approach is that implementations can be replaced at run-time. This is possible because objects are accessed only through their interfaces, so one object can be replaced with another just as long as they have the same type.

But doubt may be naive, since i am a beginner. How the implementation can be replaced in runtime? If we write a new line of code, don't we need to compile to reflect the change? Then what is meant be replacing at runtime? quite confusing. Or any other magic, behind the scene activities happen. Can anyone please reply.

like image 350
JavaGeek Avatar asked May 04 '13 05:05

JavaGeek


2 Answers

There are two strong reasons to prefer composition over inheritance:

  • Avoids combinatorial explosions in class hierarchy.
  • Can be modified at run-time

Let's say that you are writing an ordering system for a pizza parlor. You would almost certainly have a class Pizza...

public class Pizza {
    public double getPrice() { return BASE_PIZZA_PRICE; }
}

And, all else being equal, the pizza parlor probably sells a lot of pepperoni pizza. You can use inheritance for this - PepperoniPizza satisfies an "is-a" relationship with pizza, so that sounds valid.

public class PepperoniPizza extends Pizza {
    public double getPrice() { return super.getPrice() + PEPPERONI_PRICE; }
}

Okay, so far so good, right? But you can probably see that things we haven't considered. What if a customer wants pepperoni and mushrooms, for instance? Well, we can add a PepperoniMushroomPizza class. Already we have a problem - should PepperoniMushroomPizza extend Pizza, PepperoniPizza, or MushroomPizza?

But things get even worse. Let's say our hypothetical pizza parlor offers sizes Small, Medium, and Large. And crust varies too - they offer a thick, thin, and regular crust. If we are just using inheritance, suddenly we have classes like MediumThickCrustPepperoniPizza, LargeThinCrustMushroomPizza, SmallRegularCrustPepperoniAndMushroomPizza, et cetera...

public class LargeThinCrustMushroomPizza extends ThinCrustMushroomPizza {
    // This is not good!
}

In short, using inheritance to handle diversity along multiple axes causes a combinatorial explosion in the class hierarchy.

The second problem (modification at run-time) derives from this as well. Suppose that a customer looks at the price of their LargeThinCrustMushroomPizza, gawks, and decides they'd rather get a MediumThinCrustMushroomPizza instead? Now you are stuck making a whole new object just to change that one attribute!

This is where composition comes in. We observe that a "pepperoni pizza" does indeed have an "is-a" relationship with Pizza, but it also satisfies a "has-a" relationship with Pepperoni. And it also satisfies "has-a" relationships with crust type, and size. So you re-define Pizza using composition:

public class Pizza { 
    private List<Topping> toppings;
    private Crust crust;
    private Size size;

    //...omitting constructor, getters, setters for brevity...

    public double getPrice() {
        double price = size.getPrice();
        for (Topping topping : toppings) {
            price += topping.getPriceAtSize(size);
        }
        return price;
    }
}

With this composition-based Pizza, the customer can choose a smaller size (pizza.setSize(new SmallSize())) and the price (getPrice()) will respond appropriately - that is, the run-time behavior of the method may vary according to the run-time composition of the object.

This is not to say that inheritance is bad. But where it is possible to use composition instead of inheritance to express a diversity of objects (like pizzas), composition should usually be preferred.

like image 29
killscreen Avatar answered Oct 12 '22 20:10

killscreen


Think of an implementation of Stack. A simple implementation of Stack utilizes a List behind the scenes. So naively, you could extend ArrayList. But now if you want a separate Stack backed by a LinkedList instead, you would have to have two classes: ArrayListStack and LinkedListStack. (This approach also has the disadvantage of exposing List methods on a Stack, which violates encapsulation).

If you used composition instead, the List to back the Stack could be provided by the caller, and you could have one Stack class that could take either a LinkedList or an ArrayList, depending on the runtime characteristics desired by the user.

In short, the ability of the implementation to "change at runtime" refers not to an instance of the class being able to change its implementation at runtime, but rather that the class does not know at compile time what its precise implementation will be.

Also note that a class using composition need not allow the delegate implementation to be chosen at runtime (by the caller). Sometimes doing so would violate encapsulation, as it would give the caller more information about the internals of the class than is desirable. In these cases, composition still carries the benefits of only exposing the methods of the abstraction, and allowing the concrete implementation to be changed in a later revision.

Real-life examples

By the way, I use the example of Stack because it's not purely hypothetical. Java's Stack class in fact extended Vector, which made it forever carry the baggage of synchronization and the performance characteristics of an array-backed list. As a result, using that class is heavily discouraged.

A perfect example of correctly using composition for a collection can also be found in the Java library, in Collections.newSetFromMap(Map). Since any Map can be used to represent a Set (by using dummy values), this method returns a Set composed of the passed-in Map. The returned Set then inherits the characteristics of the Map it wraps, for example: mutability, thread safety, and runtime performance--all without having to create parallel Set implementations to ConcurrentHashMap, ImmutableMap, TreeMap, etc.

like image 57
Mark Peters Avatar answered Oct 12 '22 20:10

Mark Peters