Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Using Inheritance Inheritance review we have seen all of the mechanisms for creating subclasses: adding new methods replacing methods from the parent by overriding refining methods from the parent by overriding now let's see what we can do with inheritance in our programs java.io.* example various implementations for different sources of data subclasses mainly differ in construction Java's input/output classes use inheritance and composition to provide a wide range of I/O facilities to programmers composite classes, which both inherit from and contain an InputStream reuses different data sources without duplication of code adds new functionality like filtering and buffering suitable for further subclassing Principle of substitutability A derived class inherits all of the (public) attributes of it's parent. Therefore, an instance of the derived class can be substituted for an instance of the parent class. We have already seen a special case of this with interfaces: the Stack interface provides a common ancestor for SimpleStack and BetterStack client code is written in terms of the parent, Stack, because all implementations inherit the attributes of Stack, and so are substitutable. A variable which refers to objects of different classes is called a polymorphic variable. java.lang.Object.toString() in Java, all classes have java.lang.Object as a common ancestor thus all objects have a toString() method, which returns a String that represents the object if toString() is not overriden, it uses Object's implementation, which returns the object's reference (a hex value) Methods like System.out.print( Object o ) use a polymorphic variable (“o” in this case) to support many classes with just one piece of code Polymorphism poly ~ many, morph ~ form polymorphic variable – defined as one type, but may contain values of other (sub)types. overloading: a method name has many forms parameteric overloading – different forms for different parameter types weak overloading – different forms for different classes overriding: a method has different forms in derived classes there are several kinds of polymorphism in modern OO languages like Java: a more robust version of weak overloading Example: Polymorphism with Object Object currentObject; // our polymorphic variable // create some objects Integer x = new Integer( 42 ); SimpleStack s = new SimpleStack(); IntArray a = new IntArray( 10 ); // ...and somewhere to collect the strings String myObjectStrings = “”; currentObject = x; // currentObject now refers to an Integer myObjectStrings += “ “ + currentObject.toString() + “\n”; currentObject = s; // now it refers to a SimpleStack myObjectStrings += “ “ + currentObject.toString() + “\n”; currentObject = a; // now it refers to an IntArray myObjectStrings += “ “ + currentObject.toString() + “\n”; A tricky example: spot the polymorphism public class Rectangle { public Rectangle( int height, int width ) { ... } public void draw( double scale ) { ... } public void draw() { this.draw( 1.0 ); } } public class ColorableRectangle extends Rectangle { public ColorableRectangle( int height, int width, Color c ) { super( height, width ); defaultColor = c; } public void draw( double scale ) { draw( defaultColor, scale ); } public void draw( Color c, double scale ) { super.draw( scale ) // graphics code to fill in the rectangle with the color // c would go here. } private Color defaultColor; } Solution: public class Rectangle { public Rectangle( int height, int width ) { ... } public void draw( double scale ) { ... } overloading public void draw() { this.draw( 1.0 ); } } this is a polymorphic variable public class ColorableRectangle extends Rectangle { public ColorableRectangle( int height, int width, Color c ) { super( height, width ); defaultColor = c; overriding } public void draw( double scale ) { draw( defaultColor, scale ); } public void draw( Color c, double scale ) { super.draw( scale ) // graphics code to fill in the rectangle with the color // c would go here. } private Color defaultColor; } Access Control Java provides three visibility modifiers, so that you can control which classes can access which attributes of a class: public: the attribute is accessible from any code private: the member is only accessible to code that is part of the class which the attribute is part of. protected: equivalent to private, except that subclasses of the class which defines the attribute are also allowed access. There is another type of access, refered to as package or friendly access. We will talk about this more in the next Java section of the course. Example public class VisibilityExample { public int x = 1; private String s = “secret”; protected boolean b = true; public boolean getB() { return b; } public boolean setB( boolean b ) { this.b = b; } protected String spillTheBeans() { return s; } protected String changeTheSecret( String newSecret ) { s = newSecret } } public class ExampleSubClass extends VisibilityExample { public void DoSomething() { // String theSecret = s; // can't do this! String theSecret = spillTheBeans(); // but can do this. changeTheSecret( “42” ); // and can even do this! b = false; x = x + 1; } } Example cont'd public class VisibilityExample { public int x = 1; private String s = “secret”; protected boolean b = true; public boolean getB() { return b; } public boolean setB( boolean b ) { this.b = b; } protected String spillTheBeans() { return s; } protected String changeTheSecret( String newSecret ) { s = newSecret } } public class MoreExample { public static void main( String args[] ) { VisibilityExample v = new VisibilityExample(); // String theSecret = v.s; // can't do this! // String theSecret = v.spillTheBeans(); // can't do this either! // System.out.println( “v.b = “ + v.b ); // can't even do this System.out.println( “v.b = “ + v.getB() ); // but we CAN do this v.setB( false ); v.x = v.x + 1; } } Appropriate uses of visibility modifiers within a class public constants for client programmers, e.g. public static final int MAX_USERS = 1000; helper methods any state data that is not purposefully being exposed. protected interface methods private defensive programming: strive to idiot proof your classes – make it so that it is not possible to use them incorrectly methods for maintaining object state internal data structures, only if they are being purposefully exposed. In practical terms, this means the authors of subclasses will need access to the source and/or meticulous documentation. Single vs. Multiple inheritance single inheritance results in an inheritance tree, because each class has at most one parent multiple inheritance allows multiple parents, resulting in an inheritance graph Shape is a “mixin class”, designed to augment another (okay) The widgets form a diamond inheritance structure (BAD) Java does not allow a class to extend more than one parent class because: Multiple inheritance in Java diamond inheritance (e.g. SuperBrokenWidget) creates ambiguity which is hard to resolve inheritance graphs are much more difficult to understand and design than inheritance trees Java does allow a class to implement more than one interface as we will see later, there are techniques that allow us to achieve the code reuse benefits of multiple inheritance with less of the complexity in Java class inheritance an object's class defines how it is implemented define an object's implementation in terms of another object by reusing, replacing and refining parent methods is a mechanism for code reuse interface inheritance (subtyping) Interface vs Implementation Inheritance an object's type(s) define only it's interface represents where one object can be used in place of another principle of substitutability in practice, we do both when extending a Java class, though only the later when implementing an interface Don't overuse inheritance! defines static, compile time relationships can lead to situations where objects need to “morph” classes (which is not possible in strongly typed languages like Java) deep, complex hierarchies of objects create more complexity than they eliminate composition does not support polymorphism composition is marginally slower inheritance usually results in less new code to write and debug. composition does not result in dependencies between the classes involved composition is simpler for client programmers to understand: Composition vs. Inheritance large inheritance hierarchies are difficult to understand inherited attributes of an object are implicit, with composition all attributes are explicit. This can lead to the “yo-yo” problem, where the client programmer has to flip back and forth between documentation for several classes inheritance cannot hide inherited attributes from client programmers empircally, composition usually results in better reuse The result... In practice, we use both inheritance and composition, often together, to achieve good quality object oriented programs. Window Widgets Example Suppose we were building a general purpose, portable windowing library. Using just inheritance, we would arrive at a structure like this: What's wrong? client code has to choose the window type (we could fix this with a factory method though) adding a new platform (e.g. Win32Window), means subclassing every kind of window (Window, IconWindow, DialogWindow, ModalWindow...) no good solution to the latter problem using inheritance Widgets Example Cont'd the solution is to separate the windowing implementations from the windowing functionality Window, and it's subclasses, define different kinds of windows in terms of an abstract window implemenation, WindowImp Different platforms provide different WindowImp subclasses by using composition and inheritance, we have a much more flexible and clear system Delegation can be used to replace inheritance with composition to be entirely equivalent to inheritance, the delegate must receive a reference to the caller combined with interface inheritance, delegation gives us the benefits of multiple inheritance (e.g. use of mixin classes), but with less complexity in the derived class Next lecture: abstract classes, and then on to exceptions and exception handling...