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.
There are two strong reasons to prefer composition over inheritance:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With