CSC/ECE 517 Fall 2011/ch17 5b br
What is Inheritance
In object-oriented programming (OOP), inheritance is a way to reuse code of existing objects, establish a subtype from an existing object, or both, depending upon programming language support. In classical inheritance where objects are defined by classes, classes can inherit attributes and behavior from pre-existing classes called base classes or superclasses or parent classes or ancestor classes. The new classes are known as derived classes or subclasses or child classes. The relationships of classes through inheritance gives rise to a hierarchy.
Code Reuse
A milestone on the pathway to an elegant implementation of inheritance is software reuse. Software reuse, better known as code reuse, is the practice of using existing software or software knowledge to build new software programs.<ref>Frakes, W.B. and Kyo Kang, (2005), "Software Reuse Research: Status and Future", IEEE Transactions on Software Engineering, 31(7), July, pp. 529-536.</ref> A software library is a practical example of code reuse. Whether it is a large corporation or a collegiate engineering project, programmers utilize existing code repositories to reduce time spent during the development phase. In a well-designed multi-tier software program where inheritance is properly implemented code reuse is achieved through using the Don't Repeat Yourself design principle. And although inheritance provides this capability a few things should be considered during the design process <ref> 2</ref>.:
- Focus on well-defined general aspects of the software program,
- look for areas within the program where a library can be utilized or possibly developed for future reuse
- Name software asses appropriately
- Package should an category classification encapsulating all the data
- Interfaces should be unifying theme that group of classes
- Classes should be proper nouns
- Methods should be simple action verbs
- Being consistent is critical for reusing code over time
- This is most common with products that have a long life-span and continually evolve in functionality
By no means are the items listed above a comprehensive guideline for the proper implementation of an inheritance through code reuse. It is merely provided as potential assets to programmer looking for a foundational starting point. Any although inherit ace provides the ability to reuse any code; the practically of that implementation is strictly dependent on the programmer and the requirements of the application.
class Parcel_Dimension { double width; double height; double depth; // constructor when all dimension specified Parcel (double w, double h, double d){ weight = w; height = h; depth = d; } } class ParcelWeight extends Parcel_Dimension { double weight; // weight of parcel // constructor for parcel weight ParcelWeight(double w, double h, double d, double m) width = w; height = h; depth = d; weight = m; } class Shipping_Cost extends ParcelWeight { double cost; // constructor for shiping price Shipping_Cost(double w, double h, double d, double m, double c) super(w, h, d, m); cost c; } class Shipment_Printout{ public static void main(String args[]){ Shipment_cost letter1 = new Shipment_cost (10, 20, 15, 10, 3.66) System.out.println(" Weight of letter is: " + letter1.weight); System.out.println(" Shipping cost: " + letter1.cost); }
Polymorphism
Polymorphism, Greek for “many forms” is a feature that allows one interface to be used for a general class of actions. Moreover, the concept of polymorphism is often expressed by the phrase, “one interface, multiple methods,” which means implies that a general interface should be associated with a group of related actives. <ref> </ref>This in turn provides a situation that aids in the reduction of a programs complexity by allowing the same interface to be used throughout different situations The responsibility of knowing which method needs to be evoke is left up to the compiler. In respect to inheritance polymorphism should be used when a programmer needs to deal in generalities and let the execution-time environment handle the specifics. <ref> 3</ref> Proper use of polymorphism promotes extensibility, and software that invokes polymorphic behavior is independent of the object types to which messages are sent.
// Using run-time polymorphism class Figure_2D{ double dim1; double dim2; Figure_2D(double a, double b){ dim1 = a; dim2 = b; } double area(){ System.out.println("Area for Figure is undefined."); return 0; } } class Rectangle extends Figure_2D{ Rectangle (double a, double b){ super(a, b); } // overide area for rectangle double area(){ System.out.println("Inside Area for Rectangle."); return dim1*dim2; } } class Triangle extends Figure_2D{ Triangle (double a, double b){ super(a, b); } // overide area for right triangle double area(){ System.out.println("Inside Area for Triangle."); return dim1*dim2/2; } } class FindAreas{ public static void main (String args[]) { Figure r new REctangel (9, 5); Triangle t = new Triangle(10, 8); Figure figref; figref = r; System.out.println("Area is" + figref.area()); figref = t; System.out.println("Area is" + figref.area()); } == Costs of Using Inheritance == There are even more costs to be considered when using inheritance, in addition to those we’ve already addressed. One problem with inheritance, especially a deep inheritance tree with many generations, is that the code for the methods of a class low in the tree is spread out among all its ancestors higher in the tree, which makes it harder for the reader of the code to follow the flow of execution. That is, suppose someone is reading code and sees that a method foo is invoked on an object. If the object’s class does not implement foo, then the reader needs to look to the object’s immediate superclass. If that class does not implement foo, then a further search up the inheritance hierarchy needs to be made. To complicate matters, foo may invoke another method bar on the same object. There need be little relationship between the locations of foo and bar in the inheritance tree, and so the reader again needs to start at the object’s class and search up the inheritance tree, to find the implementation of bar. Matters are even worse if the reader is not sure of the object’s class and knows, for example, only that the object could belong to any of the subclasses of a given class. In such cases, it is impossible to figure out exactly which method body of which class gets executed at any given time. Another problem with inheritance is that all subclasses are very tightly tied with their superclasses. This coupling comes from the fact that, to guarantee certain behavior in a subclass, that subclass needs to know significant parts of the implementation of the methods of the superclasses. Eg: Let's examine the superclass and subclass coupling problems. The following Stack class extends Java's ArrayListclass to make it behave like a stack: <pre> class Stack extends ArrayList { private int stack_pointer = 0; public void push( Object article ) { add( stack_pointer++, article ); } public Object pop() { return remove( --stack_pointer ); } public void push_many( Object[] articles ) { for( int i = 0; i < articles.length; ++i ) push( articles[i] ); } } Even a class as simple as this one has problems. Consider what happens when a user leverages inheritance and uses theArrayList's clear() method to pop everything off the stack: Stack a_stack = new Stack(); a_stack.push("1"); a_stack.push("2"); a_stack.clear();
The code successfully compiles, but since the base class doesn't know anything about the stack pointer, the Stack object is now in an undefined state. The next call to push() puts the new item at index 2 (the stack_pointer's current value), so the stack effectively has three elements on it—the bottom two are garbage. (Java's Stackclass has exactly this problem; don't use it.)
Inheritance vs Delegation
Delegation is an alternative to inheritance for reusing code among multiple classes. Inheritance uses the IS‐A relationship for re‐use; delegation uses the HAS‐A reference relationship to do the same. Inheritance and delegation have the same kind of relationship that, both are alternatives for fixing a problem, with one more appropriate than the other in some situations. In this chapter, we will study the nature of delegation, see how we can convert an inheriting class to a delegating one, compare the advantages and disadvantages of the two approaches, and identify scenarios in which they should be used.
The main reason, as mentioned, before, for using inheritance is sharing of code among multiple classes. However, as shown in the figure, while inheritance implies code reusability, code reusability does not require inheritance. Delegation is an alternative approach to allow multiple classes to share code. In the case of inheritance, a reusing class has an IS‐A relationship with a reused class. Thus, it inherits code from the reused class. In the case of delegation, the reused class HAS‐A reference to the reused class. This reference allows it to delegate tasks to the reused class. We shall consider the same case of the Stack class extending the Java's ArrayListclass to make it behave like a stack. As discussed before, the problem is that the base class doesn't know anything about the stack pointer, the Stack object is now in an undefined state. One solution to the undesirable method-inheritance problem is for Stack to override all ArrayList methods that can modify the array's state, so the overrides either manipulate the stack pointer correctly or throw an exception.
This approach has two disadvantages. First, if you override everything, the base class should really be an interface, not a class. There's no point in implementation inheritance if you don't use any of the inherited methods. Second, and more importantly, you don't want a stack to support all ArrayList methods. That pesky removeRange() method isn't useful, for example. The only reasonable way to implement a useless method is to have it throw an exception, since it should never be called. This approach effectively moves what would be a compile-time error into run-time.
A better solution to the base-class issue is encapsulating the data structure instead of using inheritance. Here's a new-and-improved version of Stack:
class Stack { private int stack_pointer = 0; private ArrayList the_data = new ArrayList(); public void push( Object article ) { the_data.add( stack_pointer++, article ); } public Object pop() { return the_data.remove( --stack_pointer ); } public void push_many( Object[] articles ) { for( int i = 0; i < o.length; ++i ) push( articles[i] ); } }
Let us consider different scenarios under which inheritance and delegation fares each other:
Scenarios | Inheritance | Delegation |
---|---|---|
Polymorphism | If B is a subclass of A, then subtype polymorphism is possible and so an object of class B can be used anywhere an object of class A . | If B is composed with A, then this subtype polymorphism does not apply to B. |
Interface | If B is a subclass of A, then B inherits all methods of A, and so the interface of B must include all the methods in the interface of A, whether B wants them all or not. Furthermore, it is usually not appropriate to “void out” or nullify the methods of A that B doesn’t want | if B is composed with A, then the public interface of B need not be related at all to the public interface of A, and so you have the flexibility to design B exactly the way you want. |
Efficiency | If B is a subclass, there is direct execution of any inherited methods. | if B forwards requests to A, then the methods of B must call methods of A, which results in slightly higher overhead costs. |
Amount of Code | If B is a subclass of A, you need to implement in B only the methods of B that aren’t already inherited from A. | If B forwards requests to A, then you must implement all of B’s methods yourself. Although many of these methods might merely call a corresponding method in A, there is still more code to write and therefore more chance of errors. |
Dynamic Changeability | If B is a subclass of A, then at run-time, there is no way to change the behavior of the inherited methods. | If B forwards requests to A, then at run-time, B can change the object of class A or a subclass of A to which it forwards requests. |
IS-A
Inheritance allows a new class to extend an existing class. The new class inherits the members of the class it extends. When one object is a specialized version of another object, there is an "is a" relationship between them. Here are a few examples of the "is a" relationship:
1. A car is a vehicle. 2. A rectangle is a shape.
When an "is a" relationship exists between objects, it means that the specialized object has all of the characteristics of the general object, plus additional characteristics that make it special. In object-oriented programming, inheritance is used to create an "is a" relationship among classes. This allows you to extend the capabilities of a class by creating another class that is a specialized version of it.
Consider the following implementation of the Circle and Ellipse classes using inheritance. In the implementation Circle will be the sub-classclass of the Ellipse, hence it doesn’t need to implement anything but a constructor. From a geometrical perspective, every circle “is a” ellipse. Furthermore, there are certainly good opportunities for code reuse between them. Therefore, it seems natural to make the Circle class a subclass of the Ellipse class. But is this a good decision?
public class Ellipse { private int x, y, width, height; public Ellipse(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 void setSize(int w, int h) { width = w; height = h; } } public class Circle extends Ellipse { public Circle(int x, int y, int width) { super(x, y, width, width); } }
Because of the fact that subclasses inherit all methods of their superclasses, the Circle class now inherits a setSize method that has two parameters. A call to this method can make the width and height of the Circle unequal, a rather undesirable outcome. A setSize method for Squares should just take one parameter. This can be avoided by nullifying the negative effects of the inherited setSize method by overriding it in the subclass. For example, we might add the following method to the Circle class:
public void setSize(int w, int h) { width = h; height = h; }
thereby allowing users to modify the size of the circle.
Our problem here is that the subclass does not have behavior consistent with the behavior of its superclass. Such consistent behavior is necessary for elegant code. This thereby ensure the Principle of Least Astonishment.
Let us consider the inconsistent behavior between the Ellipse Class and the Circle class, with respect to the Liskov's Substitution Principle(LSP) . The setSize method of the Ellipse class has the behavior of modifying the width independently of the height. A setSize method of the Cirlce class can- not have that behavior and still preserve squareness, and therefore the Circle class’ setSize method does not do everything that the Ellipse class’ setSize method does. The conclusion is that Circle should not be a subclass of Ellipse.
Public Interfaces
If class S responds to all the messages that class C responds to, and then some, it seems appropriate for S to be a subclass of C. Let us consider the following case: A video store application which has a Person class, with name, address fields. It also has a Staff class, which has name, address fields and a Customer Class which also name, address fields. A simple object oriented approach would be to say that a Customer "is a" Person also Staff “is a” Customer, therefore create classes as so.
public class Person { private String name; private String address; public String getAddress() { return address; } … } public class public class Customer extends Person { private String CustomerID; ... } public class Staff extends Person { private String StaffID; ... }
This system works fine until we have someone who is both a customer and a member of staff. Assuming that we don't really want our everyone list to have the same person in twice, once as a Customer and once as a Staff, we make an arbitrary choice between: It would be overly complex and difficult to maintain if you attempt to have a set of derived classes that implement the People. This is especially true given that the above example is very simple - in most real applications, things will be more complex. In this case, we would go with different approach. I would implement the Person class and include in it a collection of "roles". Each person could have one or more roles such as "Customer" and "Staff”. This will make it easier to add roles as new requirements are discovered. For example, you may simply have a base "Role" class, and derive new roles from them. In this way, the Person object can be considered to exist permanently, but the person’s roles can come and go. Furthermore, there is no duplication of data.
Conclusion:
References:
http://www.kev.pulo.com.au/pp/RESOURCES/C++-FAQ/proper-inheritance.html#[21.6](Circle and Ellipse)
http://msdn.microsoft.com/en-us/library/27db6csx(v=vs.80).aspx (Is-A relationship)
http://pages.cs.wisc.edu/~cs368-1/JavaTutorial/NOTES/Inheritance-intro.html(Inheritance)
http://javaboutique.internet.com/tutorials/JavaOO/(LSP)
http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=1(Inheritance vs Delegation)