CSC/ECE 517 Fall 2011/ch17 5b uo: Difference between revisions
Line 167: | Line 167: | ||
== Public Interface == | == Public Interface == | ||
Public Interface define the behavior/properties of a class that is visible to other objects. In the classic Squares and Rectangles example, the reason why we could not use inheritance was because, the public methods of rectangle class were not all appropriate for square class. Consider an example below | |||
<pre> | |||
public class Rectangle | |||
{ | |||
private int x, y, width, height; | |||
public Rectangle(int x, int y, int w, int h) { | |||
this.x = x; this.y = y; width = w; height = h; | |||
} | |||
public int getWidth() { return width; } | |||
public int getHeight() { return height; } | |||
public int getArea() { return width * height; } | |||
public int getPerimeter() { return 2 * (width + height); } | |||
public void setSize(int w, int h) { width = w; height = h; } | |||
public void stretch(Rectangle r, int dx,){ | |||
r.setSize(r.getWidth() + dx, r.getHeight()); | |||
} | |||
} | |||
public class Square extends Rectangle | |||
{ | |||
public Square(int x, int y, int side) { | |||
super(x, y, side, side); | |||
} | |||
} | |||
</pre> | |||
In this example, most of the methods that are meant for rectangle can be used by square. Method stretch is a public method that stretches one of the sides of the rectangle. If we use this method for square, the square ceases to be a square and becomes a rectangle. This is an undesirable behavior. However, we can fix this by deleting the stretch method or by setting stretch as a private method. Upon doing so, we would not be able to invoke the stretch method for squares and all of the other methods would result in similar/expected behavior. | |||
= <b>Composition vs Inheritance </b>= | = <b>Composition vs Inheritance </b>= |
Revision as of 02:18, 2 November 2011
When to use Inheritance? - Introduction
Inheritance is a useful programming concept, but it is easy to abuse this concept. Often interfaces [1] or delegation [2] are better options. In this wiki chapter, we will discuss the situations when to use and when not to use inheritance. In general, the following cases 'almost' justify the use of Inheritance
- Representing "is-a" relationship
- Reusing code
- Implementing polymorphism
- Public Interface
These points are discussed in detail below
Representing "Is-a" Relationship
A "Is-a" relationship can be represented by inheritance in most cases.For example cat "Is-a" animal. Lets consider a scenario of an organization where a person can either be a manager or an employee. Thus a employee "Is-a" person. Can this "Is-a" relationship be represented by inheritance? Let us assume that an employee can be represented as a subclass of person. What happens when the employee is promoted to become a manager? In this case when a employee is promoted to become a manager, all the references of the employee has to be removed which can be cumbersome. Hence the employee "Is-a" person relation cannot be represented by inheritance. So how do we represent this relationship? This problem can be solved by using referencing in the following way:
public class Person { private String name; private String gender; private String address; public String getAddress() { return address; } ...other methods and data... }
public class employee { private Person me; public String getAddress() { return me.getAddress(); } public float getSalary() {} ...other methods and data... }
Hence a class x shouldn't be a super class of class y based on a role played by y that is modeled by x.
A "Is-a" relationship between a super class S and a subclass T can be represented by inheritance only if S and all its references can be replaced by T without losing correctness of the program. This is called Liskov's Substitution Principle (LSP) [3].
An example where LSP determines the fate of a "Is-a" relationship is the well-known square is a rectangle scenario. Even though a square is a rectangle it cannot be a subclass of rectangle because all the references to a rectangle cannot be replaced by a square.Let us assume we replace a rectangle object of length 10 and height 5 by a square object of length 10. The area of the square will be 100 and cannot replace the rectangle whose area is 50. In order to use inheritance to implement a class B which is identical to class A except that it has extra restrictions on its state should not be a subclass of A, both classes should be immutable (objects of the class cannot have their state changed in a way that is noticeable to outside objects)
Code Reuse
One of the popular reasons to use inheritance is to take the advantage of code reuse. The idea of reusability is one of the key components of Object-oriented (OO) design. OO Programming encapsulates attributes and methods that manipulates the data into one small unit, a unit that can be instantiated multiple times, hence supporting reusabilty of code. If the classes are well-designed classes, they can be debugged once and used over and over as a basis for new classes. Inheritance enables reusabilty of code and removal of redundant code by confirming to the DRY (Dont repeat yourself) [4] (aka DIE - Duplicating is Evil) principle. In inheritance the subclass inherits all the attributes and methods of the superclass, hence, removing the need of defining common attributes and methods in two different classes. But representing the relationship between objects having some common attributes and/or methods by inheritance might not be appropriate all the time. The rule of thumb would be to evaluate if the relationship satisfies the same two principles required by a "Is-a" relationship to be represented as inheritance. Furthermore, there are other ways of achieving code reuse such as composition. Consider an example below
Case 1: Using Inheritance for code reuse 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(); } }
Case 2: Using Composition class Fruit { 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(); } }
In both the cases we have used DRY principle. In the first case, the subclass Apple automatically inherits the implementation of peel method in the superclass Fruit. In the second case, the subclass Apple becomes the front-end class and superclass Fruit becomes the back-end class and Apple class explicitly invokes the Peel method in the back end class from its own implementation of the method. This explicit call is sometimes called "forwarding" or "delegating" the method invocation to the back-end object.
Inheritance-based Polymorphism
Polymorphism [5] meaning 'many forms' has the same meaning in Object Oriented programming language as it is the ability to have an attribute, method or an object in more than one form. Operator overloading [6] is a good example of polymorphism. In java a "+" operator adds two integer type operands while it concatenates two string type operands. Hence the "+" operator can exist in multiple forms. Inheritance provides an opportunity for polymorphism by leveraging method overloading or method overriding. Method overriding is a means by which a subclass can override methods inherited from superclass. The next question that follows is when and how method overriding can be useful. Lets assume a duck class and a hen class. Duck "Is-a" bird. Hen also "Is-a" bird. They have a lot in common but they produce different sounds. So, having bird as a superclass with a talk method that is overridden by the subclasses hen and duck would make a good program.
public class bird { public String name; public bird(String name) { this.name = name; } public String sounds() { return "Unknown bird!!"; } }
public class duck extends bird { public duck(String name) { super(name); } @Override public String sounds() { return "quack!" ; } }
public class hen extends bird { public hen(String name) { super(name); } @Override public String sounds() { return "cocock!"; } }
public class overridingLeveragedByPolymorphism { public static void main(String[] args) { ArrayList<bird> birds = new ArrayList<bird>(); birds.add(new hen("chicken")); birds.add(new duck("duckling")); for (int i=0;i<birds.size();i++) { thisBird = birds.get(i); System.out.println(thisBird.name + " sounds " + thisBird.sounds()); } } }
//output
//chicken sounds cocock!
//duckling sounds quack!
Public Interface
Public Interface define the behavior/properties of a class that is visible to other objects. In the classic Squares and Rectangles example, the reason why we could not use inheritance was because, the public methods of rectangle class were not all appropriate for square class. Consider an example below
public class Rectangle { private int x, y, width, height; public Rectangle(int x, int y, int w, int h) { this.x = x; this.y = y; width = w; height = h; } public int getWidth() { return width; } public int getHeight() { return height; } public int getArea() { return width * height; } public int getPerimeter() { return 2 * (width + height); } public void setSize(int w, int h) { width = w; height = h; } public void stretch(Rectangle r, int dx,){ r.setSize(r.getWidth() + dx, r.getHeight()); } } public class Square extends Rectangle { public Square(int x, int y, int side) { super(x, y, side, side); } }
In this example, most of the methods that are meant for rectangle can be used by square. Method stretch is a public method that stretches one of the sides of the rectangle. If we use this method for square, the square ceases to be a square and becomes a rectangle. This is an undesirable behavior. However, we can fix this by deleting the stretch method or by setting stretch as a private method. Upon doing so, we would not be able to invoke the stretch method for squares and all of the other methods would result in similar/expected behavior.
Composition vs Inheritance
Drawbacks of inheritance
Inheritance is not without its own set of drawbacks. If inheritance is applied without due consideration problems can arise. In some situations it can:
- Reduce the comprehensibility of code.
- Make maintenance harder.
- Make further development harder.
- Reduce reliability of code.
- Reduce overall reuse.
Conclusion
<EDIT THIS> We have considered the benefits and drawbacks of inheritance within an object oriented programming language. We have challenged the general perception that inheritance is by its very nature always good and have considered when it should and should not be used. We have re-assessed compositional reuse and made the case that it is as important, in an object oriented language, as inheritance in order to achieve the maximum possible reuse. We can therefore provide a summary of our findings that can be used as a set of guiding principles for object oriented development: · Avoid code dependency except on published protocol. · For structural inheritance direct extension is fine.
References
- http://c2.com/cgi/wiki?CompositionInsteadOfInheritance [7]
- http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=1[8]
- http://msdn.microsoft.com/en-us/library/27db6csx%28v=vs.80%29.aspx[9]
- http://ivan.truemesh.com/archives/000490.html[10]
- http://littletutorials.com/2008/06/23/inheritance-not-for-code-reuse/[11]
- http://msdn.microsoft.com/en-us/library/ms973861.aspx#interinher_topic3[12]