300 likes | 394 Views
Lecture 14. Inheritance vs Composition. Inheritance vs Interface. Use inheritance when two objects share a structure or code relation Embodies the is_a relation Should reflect the roles of the objects throughout the program Use an interface when they share a common behavior spec.
E N D
Lecture 14 Inheritance vs Composition
Inheritance vs Interface • Use inheritance when two objects share a structure or code relation • Embodies the is_a relation • Should reflect the roles of the objects throughout the program • Use an interface when they share a common behavior spec
Abstract Classes vs Interfaces • Abstract for the inheritance situation above, i.e. sharing code or structure • Interfaces for sharing behavior spec, not code • Example: Consider a Framework • It provides code in the form of methods that are inherited without being overridden->code is inherited • The implementation of an action listener cannot be predicted, so no code is inherited here
Inheritance and Composition • Which to use and when? • Alternative 1: Implement a Stack with inheritance from a Vector class Stack extends Vector{ public Object push(Object item){ addElementAt(size()-1;} public Object pop(){return elementAt (size()- 1);} • Stacks can use protected members of Vector • Stacks can be used where Vectors are used as arguments • Reuse Vector methods, e.g. size, isEmpty • What to do with unwanted Vector methods, e.g. removeAt()?
Inheritance and Composition • Now do this with Composition: class Stack{ private Vector theData; public Stack(){theData=new Vector();} public Object push(Object item){theData.addElement( item); return item;} • Advantages • Can change the implementation without any impact to users of stacks • Interface is narrower: we don’t need to know anything about Vectors
Inheritance and Composition • Advantages to Composition (cont.) • There are no substitutability issues • Stacks and Vectors are different types • One cannot be substituted for the other • Meaningless behavior is not exposed • Inheritance couples base and derived class • Changes do not ripple upwards • Promotes encapsulation • Not dependent on private variables • Can change implementation of composed objects at run-time, not so with inheritance
Composition • Prefer it to inheritance • Used in Java AWT • Uses Components and Containers • An item is a Component • A Container can contain Components and Containers • Obtain a tree-like structure by nesting • Embodied in the Composite Pattern
Composite Pattern • Facilitate the same treatment of composite and primitive objects • Composite object: an object that contains other objects • E.g. lines and polygons are primitive objects, a drawing is composite. • Composite methods are implemented by iterating over the composite object, invoking the appropriate method for each subcomponent
Composite Pattern • Use it when • You want to represent part-whole hierarchies of objects • You want your clients to be able to ignore differences between compisitions of objects and objects themselves • Benefits • Easy to add new kinds of components • Makes clients simpler
Composite Pattern • Liabilities • Hard to restrict the types of components • Clients can do meaningless things to primitive objects at run-time
Example • Consider a simple GUI system: public class window{ Button[] buttons; TextArea[] ta; Menu[] menus; WidgetContainer[] containers; public void updateWindow(){ if (buttons != null){ for(k=0;k<buttons.length();k++) buttons[k].draw(); if (ta != null) ... }
Problem • If you want to add on a new kind of resource the update() method needs to be modified • Way around this is to use a uniform interface • Just do Widgets and WidgetContainers • Now you are programming to an interface • All Widgets support the Widget interface
Another Attempt public class window{ Widget[] widgets; WidgetContainer[] containers; public void updateWindow(){ if (widgets != null) for (k=0; k<widgets.length(); k++){ widgets[k].updateWindow(); if (containers != null) ....
Now Use Composite Pattern Component Button Menu widgetContainer
To Obtain public class window{ Component[] components; public void updateWindow(){ if (components != null) for(k=0;k<components.length();k++) components[k].updateWindow(); } } • Bottom Line: Do not distinguish Widgets and WidgetContainers
Delegation in Composition • It is often convenient to allow a receiving object, e.g. a Window, to further delegate certain operations to another object--its delegate--e.g. a Rectangle • This is better than making Window a subclass of Rectangle, the Window class may reuse the behavior of Rectangle and delegate rectangle-specific behavior to it
Window Rectangle Rectangle area() area() width height return Rectangle->area() return width*height
Comments • Here Window has_a Rectangle • It is easy to compare behavior at run-time • You can reuse Rectangle as a black box (not white-box as in inheritance) • You can use polymorphism to achieve dynamic behavior--employ an interface. This is the Strategy Design Pattern • Trade-off: harder to understand, much more flexible than inheritance • Visitor uses delegation
Combining Inheritance and Composition • Main use: simplify an inheritance hierarchy • Not achievable everywhere, but it is very powerful where you can • Main example: Java Stream Wrappers • Add capabilities to a stream by “wrapping” it in another object that provides the desired capabilities • Makes a bigger, better version of a base class
InputStream Hierarchy • InputStream • ByteArrayInputStream • FileInputStream • PipedInputStream • SequenceInputStream • ObjectInputStream • FilterInputStream • BufferedInputStream • DataInputStream differ in the source of data values
The Way It Works • Start with an InputStream, i.e. try to read a stream of bytes in sequence • Use it polymorphically • Add functionality, depending on your data • This new functionality is called a “wrapper” • Just add a new and better interface to the old one, getting/sending the result from/to the same place, i.e. InputStream, resp. OutputStream • Subclassing provides the new interface
Example class FilterInputStream extends InputStream ... protected InputStream in; FilterInputStream(InputStream in){ this.in = in;} ...} • Filter is a wrapper • Builds on InputStream • First obtain the sequence of bytes from the InputStream and then do the filtering • Use composition on the InputStream
Comments • You really have one object, many interfaces • Thus you avoid an explosion of the inheritance hierarchy • FilterInputStream is really just an InputStream with added functionality • If you wanted a DataInputStream, just wrap the InputStream in a DataInputStream object • Construct a new DataInputStream object, using the InputStream as input to the constructor
Important Note • The primitive data-type operations of DataInputStream cannot be included in InputStream, because that object does not know about integer, float, etc • But you can make it understand these types by wrapping. • The neat thing: You can use a DataInputStream anywhere an InputStream is expected--thi is the advantage of inheritance
Example • If dataSource is of type InputStream: InputStream dataSource; • Then the DataInputStream: DataInputStream typedDataSource = new DataInputStream(dataSource); • Gets the data from exactly the same place as data retrieved from dataSource • We have merely provided a better interface to the same input stream
Another Example: Buffered Readers • Use Readers and Writers for character data • Start with primitive readers that directly manipulate the input data: • CharArrayReader, StringReader, FileReader • Now add functionality to data generated by the above Readers: • BufferedReader, FilterReader, LineNumberReader • Reader has the subclasses • BufferedReader • FileReader • PipedReader
One Way to Buffer the Char Input • Do it from FileReader: BufferedReader in =new BufferedReader( new FileReader(“stuff.in”); • Buffers the char file “stuff.in”
Another Way • Start with Reader (which is also primitive for 16 Bit Unicodes) • Buffer on top of it • Allow two types of buffer--a standard one with 8192 bytes and another of user-specified size • Use Reader’s capabilities and then add the buffering in the constructor
public class BufferedReader extends Reader{ private Reader in; //use composition private char cb[]; //the buffer private static int defaultCharBufferSize=8192; private static int defaultExpectedLineLength=80; public BufferedReader(Reader in, int sz){ super(in); //get Reader functionality if (sz <= 0) throw new IllegalArgumentException( “Illegal Buffer size”); this.in = in; cb = new char[sz]; nextChar = nChars = 0;} public BufferedReader(Reader in){ this(in, defaultCharBufferSize);}
Remarks • BufferedReader has methods read() and readLine() to read a single character, resp. a line of text. • Both of these throw an IOException • Remember to import from java.io