Inheritance vs Composition?
I asked similar question one month ago: Inheritance though composition?
I took the examples from this article: http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=1 However, this time is a different problem. Sorry for posting a lot of codes, but it is just long, it's not difficult to read.
class Fruit {
public int peel() {
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple extends Fruit {
}
class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
After modification, it's not longer ok:
class Peel {
private int peelCount;
开发者_JAVA百科 public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
Modification of class Fruit results in a compile error code.
class Peel {
private int peelCount;
public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
class Fruit {
// Return a Peel object that
// results from the peeling activity.
public Peel peel() {
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
The old implementation is broken. That problem can be solve by composition:
class Peel {
private int peelCount;
public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
class Fruit {
// Return int number of pieces of peel that
// resulted from the peeling activity.
public Peel peel() {
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
// Apple must be changed to accomodate
// the change to Fruit
class Apple {
private Fruit fruit = new Fruit();
public int peel() {
Peel peel = fruit.peel();
return peel.getPeelCount();
}
}
// This old implementation of Example2
// still works fine.
class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
With the style of composition, apple is no longer is-a relationship with Fruit. It become a has-a. Methods from Fruit are delegated to Apple as inheritance. Here my questions arise:
With wrapper methods to delegate methods from Fruit, isn't that promote copy and paste, which is a bad practice? Image if we have around 10-50 methods in Fruit. How can it is possible to inherit by composition, if there are around 50 subclasses to inherit?
About the first example, inheritance by using extension, the author suggests that one change in the superclass will break the implementations using it. However, I wonder, why does it need to be changed? Isn't one of the OO principle "close for modification, open for extension"? In the case of author stated, I can still keep the old superclass implementation and still make it adapt to the new change. Like this:
class Fruit {
// Return a Peel object that // results from the peeling activity. private Peel getPeel() { System.out.println("Peeling is appealing."); return new Peel(1); } public int peel(){ Peel peel = getPeel(); return peel.getPeelCount(); }
}
Is there something wrong with adding new methods to superclass to adapt instead of changing or replacing the old methods, which will break the program structure? As far as I see, by doing this, I can still achieve the same thing as the composition example, as well as I won't have to make a lot of wrapper methods to delegate methods of superclass for each subclass.
- Because of the above reason, I conclude myself that the only time using composition is better when there are multiple inheritances, or when I need to share behaviors which vary and do not belong to one class family. For example:
interface Talkative{
public void talk();
}
Animal class family can implement Talkative interface as well as Person class family. What do you think about my conclusion?
Basically, you seem to be trying to avoid changing clients that depend on an old API by creating a wrapper class.
Unfortunately, this doesn't work very well. It results in a proliferation of wrapper classes, and other cruft to create the instances of the wrapper classes. If you do to much of this, your codebase / application gets bloated, performance suffers, and your code gets increasingly hard to understand.
Better approaches are:
- Try to get the APIs right in the first place. (Duh!)
- If the API change doesn't add significant value or fix something important, put off making it.
- When you do need to make an API change, do it in a binary compatible way; e.g. add new methods rather than making binary incompatible changes to existing ones. Mark old methods as deprecated if you plan to get rid of them.
- When you have to make a binary incompatible API change, change all of the code that uses it at the same time.
The @Deprecated tag allows you to give people your APIs warning that a method should no longer be used, while giving them a window in which to fix their code to use the replacement method / alternative approach.
精彩评论