Referring to the below link:
http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=2
The composition approach to code reuse provides stronger encapsulation than inheritance, because a change to a back-end class needn't break any code that relies only on the front-end class. For example, changing the return type of Fruit's peel() method from the previous example doesn't force a change in Apple's interface and therefore needn't break Example2's code.
Surely if you change the return type of peel()
(see code below) this means getPeelCount()
wouldn't be able to return an int
any more? Wouldn't you have to change the interface, or get a compiler error otherwise?
class Fruit {
// Return int number of pieces of peel that
// resulted from the peeling activity.
public int peel() {
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple {
private Fruit fruit = new Fruit();
public int peel() {
return fruit.peel();
}
}
class Example2 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
If you would change
Fruit.peel()
's return type, you would have to modifyApple.peel()
as well. But you don't have to changeApple
's interface.Remember: The interface are only the method names and their signatures, NOT the implementation.
Say you'd change
Fruit.peel()
to return aboolean
instead of a int. Then, you could still letApple.peel()
return anint
. So: The interface ofApple
stays the same butFruit
's changed.If you would have use inheritance, that would not be possible: Since
Fruit.peel()
now returns a boolean,Apple.peel()
has to return anboolean
, too. So: All code that usesApple.peel()
has to be changed, too. In the composition example, ONLYApple.peel()
's code has to be changed.Well, in the composition case,
Apple.peel()
's implementation needs to be updated, but its method signature can stay the same. And that means the client code (which usesApple
) does not have to be modified, retested, and redeployed.This is in contrast to inheritance, where a change in
Fruit.peel()
's method signature would require changes all way into the client code.Return type of
Fruit.peel()
is being changed from int toPeel
. This doesn't meant that the return type ofApple.peel()
is being forced to change toPeel
as well. In case of inheritance, it is forced and any client usingApple
has to be changed. In case of composition,Apple.peel()
still returns an integer, by calling thePeel.getPeelCount()
getter and hence the client need not be changed and henceApple
's interface is not changed ( or being forced to be changed)With a composition, changing the class
Fruit
doesn't necessary require you to changeApple
, for example, let's changepeel
to return adouble
instead :Now, the class
Apple
will warn about a lost of precision, but yourExample2
class will be just fine, because a composition is more "loose" and a change in a composed element does not break the composing class API. In our case example, just changeApple
like so :Whereas if
Apple
inherited fromFruit
(class Apple extends Fruit
), you would not only get an error about an incompatible return type method, but you'd also get a compilation error inExample2
.** Edit **
Lets start this over and give a "real world" example of composition vs inheritance. Note that a composition is not limited to this example and there are more use case where you can use the pattern.
Example 1 : inheritance
An application draw shapes into a canvas. The application does not need to know which shapes it has to draw and the implementation lies in the concrete class inheriting the abstract class or interface. However, the application knows what and how many different concrete shapes it can create, thus adding or removing concrete shapes requires some refactoring in the application.
Example 2 : Composition
An application is using a native library to process some data. The actual library implementation may or may not be known, and may or may not change in the future. A public interface is thus created and the actual implementation is determined at run-time. For example :
So, as you can see, the composition may offer some advantage over inheritance in the sense that it allows more flexibility in the code. It allows the application to have a solid API while the underlaying implementation may still change during it's life cycle. Composition can significantly reduce the cost of maintenance if properly used.
For example, when implementing test cases with JUnit for Exemple 2, you may want to use a dummy processor and would setup the
DataProcessorManager
to return such adapter, while using a "real" adapter (perhaps OS dependent) in production without changing the application source code. Using inheritance, you would most likely hack something up, or perhaps write a lot more initialization test code.As you can see, compisition and inheritance differ in many aspects and are not preferred over another; each depend on the problem at hand. You could even mix inheritance and composition, for example :
Granted, this last example is not clean (meaning, avoid it), but it shows how composition can be used.
Bottom line is that both examples,
DataProcessor
andShape
are "solid" classes, and their API should not change. However, the adapter classes may change and if they do, these changes should only affect their composing container, thus limit the maintenance to only these classes and not the entire application, as opposed to Example 1 where any change require more changes throughout the application. It all depends how flexible your application needs to be.The key word in the sentence is "interface".
You'll almost always need to change the
Apple
class in some way to accomodate the new return type ofFruit.peel
, but you don't need to change its public interface if you use composition rather than inheritance.If
Apple
is aFruit
(ie, inheritance) then any change to the public interface ofFruit
necessitates a change to the public interface ofApple
too. IfApple
has aFruit
(ie, composition) then you get to decide how to accomodate any changes to theFruit
class; you're not forced to change your public interface if you don't want to.