/* --- CSU213 Spring 2005 Lecture Notes --------- Copyright 2005 Viera K. Proulx Lecture 11: Who's Affraid of Abstractions? */ /* Goals: - learn to abstract over lists with a list of Object - understand the benefits and limitations of such abstraction Introduction: Let us consider the following examples of classes that represent images saved in a computer, items in a salad (tomaotes, peppers, mushrooms), or segments of a car trip between refueling, with information about the origin and destination, the miles traveled, and the fuel information. +--------------+ +-------------+ +-------------+ | Picture | | Item | | Segment | +--------------+ +-------------+ +-------------+ | String title | | String name | | String from | | int width | | int pieces | | String to | | int height | | int price | | int miles | +--------------+ +-------------+ | int gallons | | int price | +-------------+ Here are some examples of the instances of these classes: Picture snow = new Picture("Snow.gif", 400, 200); Picture beach = new Picture("Beach.gif", 200, 300); Picture bluff = new Picture("Bluff.gif", 200, 600); Item tom = new Item("tomato", 3, 50); Item pep = new Item("pepper", 2, 60); Item rad = new Item("raddish", 6, 10); Segment b2n = new Segment("Boston", "NYC", 200, 10, 210); Segment n2t = new Segment("NYC", "Trenton", 120, 5, 200); Segment t2p = new Segment("Trenton", "Philly", 80, 2, 180); We need a list of pictures to represent all pictures in a folder or a directory, we need a list of salad items to represent a salas serving in a restaurant, and we need a list of trip segments, to compute the total distance traveled, or the cost of the trip. The class diagrams for these classes are as follows: +------+ +------+ | ALoP |<------------+ | ALoI |<---------+ +------+ | +------+ | / \ | / \ | --- | --- | | | | | -------------- | ------------ | | | | | | | +-------+ +---------------+ | +-------+ +------------+ | | MTLoP | | ConsLoP | | | MTLoI | | ConsLoI | | +-------+ +---------------+ | +-------+ +------------+ | | Picture first | | | Item first | | | ALoP rest |-+ | ALoI rest |-+ +---------------+ +------------+ +------+ | ALoS |<------------+ +------+ | / \ | --- | | | -------------- | | | | +-------+ +---------------+ | | MTLoS | | ConsLoS | | +-------+ +---------------+ | | Segment first | | | ALoP rest |-+ +---------------+ And the examples of lists are: ALoP pictures = new ConsLoP(snow, new ConsLoP(beach, new ConsLoP(bluff, new MTLoP())))); ALoI salad = new ConsLoI(tom, new ConsLoI(pep, new ConsLoI(rad, new MTLoI())))); ALoP trip = new ConsLoP(b2n, new ConsLoP(n2t, new ConsLoP(t2p, new MTLoP())))); It is clear, that this calls for abstraction. All three class diagrams are nearly identical. The only difference is the type of data that is the 'first' item in the list. In Java, the entire class hierarchy starts with the class Object, and every class in Java extends Object. That means, we can define instead a list of Object-s: +--------+ | ALoObj |<------------+ +--------+ | / \ | --- | | | -------------- | | | | +---------+ +--------------+ | | MTLoObj | | ConsLoObj | | +---------+ +--------------+ | | Object first | | | ALoObj rest |-+ +--------------+ We now have to make sure we can define the same lists, using these classes. That part is easy: ALoObj objPictures = new ConsLoObj(snow, new ConsLoObj(beach, new ConsLoObj(bluff, new MTLoObj())))); ALoObj objSalad = new ConsLoObj(tom, new ConsLoObj(pep, new ConsLoObj(rad, new MTLoObj())))); ALoObj objTrip = new ConsLoObj(b2n, new ConsLoObj(n2t, new ConsLoObj(t2p, new MTLoObj())))); Let us now design some methods for this class and see how it compares with the methods we designed earlier. To count the number of elements of a list we had the method count: // in the class ALoP: // count the number of elements in this list abstract int count(); // in the class MTLoP: int count() { return 0; } // in the class ConsLoP: int count() return 1 + this.rest.count(); } The method is the same in the other two class hierarchies, and will look identical in the classes that represent a list of Object-s. (We did not go through the whole design recipe, as we have seen this method several times already.) --------------------------------------------------------------------------- *************************************************************************** Next, we would like to know whether the list contains the given object. The purpose statement and the header for the method is // does this list contain the given object boolean contains(Object obj) Here are some examples: pictures.contains(snow) --> should be true pictures.contains(new Picture("Lake", 200, 200)) --> should be false salad.contains(pep) --> should be true salad.contains(new Item("Olive", 10, 10)) --> should be false trip.contains(t2p) --> should be true trip.contains(new Segment("DC", "Norfolk", 150, 8, 180)) --> should be false Let us work on designing this method. In the abstract class, the purpose and the header will be as shown above, the only change being that the method is declared to be abstract. In the class that represents the empty list, there are no items to consider, as an empty list does not contain any objects, and so the method always produces a 'false' value: // in the class MTLoObj (or MTLoP, MTLoI, MTLoS) boolean contains(Object obj){ return false; } Let us design the method for each of the three specific nonempty lists: // in the class ConsLoP: boolean contains(Picture p){ return this.first.equals(p) || this.rest.contains(p); } // in the class ConsLoI: boolean contains(Item i){ return this.first.equals(i) || this.rest.contains(i); } // in the class ConsLoS: boolean contains(Segment s){ return this.first.equals(s) || this.rest.contains(s); } The method 'equals' is defined in the class Object and so, if we replace the specific types of arguments with the 'generic' Object, the method will work as well as it did before: // in the class ConsLoObj: boolean contains(Object o){ return this.first.equals(o) || this.rest.contains(s); } We should rerun our earlier examples to make sure this abstraction works as desired. --------------------------------------------------------------------------- *************************************************************************** We now want to find more information about our lists. We are interested in the total size of all pictures we want to download, we need to know the cost of the salad serving, and we are curious about the total distance we traveled on our trip. We first need a method in the class Picture that computes its size, and a method in the class Item that computes teh cost of one item. As an exercise follow the design recipe to design these methods. The result will be // in the class Picture: // compute the size of this picure int size(){ return this.width * this.height; } // in the class Item: // compute the cost of this salad item int cost(){ return this.pieces * this.price; } We can now design the method totalSize in the classes that represent the list of pictures: // in the class ALoP: // compute the total size of all pictures in this list abstract int totalSize(); Here is an example: pictures.totalSize() == 260000; We get: // in the class MTLoP: int totalSize(){ return 0;} The template for the class ConsLoP is: ... this.first ... ... this.first.size() ... ... this.rest ... ... this.rest.totalSize() ... and the method body becomes: // in the class ConsLoP: int totalSize(){ return this.first.size() + this.rest.totalSize(); } We can now design the method totalCost in the classes that represent the list of salad items: // in the class ALoI: // compute the total cost of all items in this list abstract int totalCost(); Here is an example: salad.totalCost() == 330; We get: // in the class MTLoI: int totalSize(){ return 0;} The template for the class ConsLoI is: ... this.first ... ... this.first.cost() ... ... this.rest ... ... this.rest.totalCost() ... and the method body becomes: // in the class ConsLoI: int totalCost(){ return this.first.cost() + this.rest.totalCost(); } We can now design the method totalDistance in the classes that represent the list of trip segments: // in the class ALoS: // compute the total distance of all trip segments in this list abstract int totalDistance(); Here is an example: trip.totalDistance() == 400; We get: // in the class MTLoS: int totalDistance(){ return 0;} The template for the class ConsLoS is: ... this.first ... ... this.first.miles ... (as well as other field selectorss...) ... this.rest ... ... this.rest.totalDistance() ... and the method body becomes: // in the class ConsLoS: int totalDistance(){ return this.first.miles + this.rest.totalDistance(); } We see that the only differences are in the Cons--- classes, and even there only in the part that determines the value of the first item: its size, its cost, or the distance it represents. We can rename the methods 'size' and 'cost' to 'value', and add a similar method to the class Segment as follows: // in the class Picture: int value(){ return this.width * this.height; } // in the class Item: int value(){return this.pieces * this.price; } // in the class Segment: int value(){ return this.miles; } We can then try to abstract the methods 'totalSize', 'totalCost', and 'totalDistance' into a method 'totalValue' in the classes that represent a list of Object-s. We get: // in the class ALoObj: // compute the total value of all elements in this list abstract int totalValue(); Here are our examples: objPictures.totalValue() == 260000; objSalad.totalValue() == 330 objTrip.totalValue() == 400; We get: // in the class MTLoS: int totalDistance(){ return 0;} The template for the class ConsLoObj is: ... this.first ... ... this.rest ... ... this.rest.totalValue() ... If we could include in the template ... this.first.value() ... our method body would be: // in the class ConsLoObj: int totalValue(){ return this.first.value() + this.rest.totalValue(); } But we have a problem. Not every class whose instances that can be included in this list contains the method 'value'. On the other hand, the three classes Picture, Item, and Segment have nothing else in common. There is no reason to define a common superclass, as this one method is the only feature they share. We need a way to express the fact that these three classes share a common functional behavior and to indicate in the method 'totalValue' in the class ConsLoObj that the 'this.first' object has this behavior. There are three parts to this 'contractual obligation': First, we define an interface (the contractual behavior) as follows: interface IValue{ // produce a value of this object int value(); } It looks like a definition of an abstract class, but contains no fields. While each class can extend only one super class, it may implement several different interfaces, as they impose no restrictions on the kind of data that the class represents --- it only requires that a specified behavior is implemented. As a second part of our contractual obligation, the three classes declare their intent to implement the 'value' emthod with the specified header. Our three class definitions will then start with: // to represent a picture in a file directory class Picture implements IValue{... // to represent an item in a salad serving class Item implements IValue{... // to represent a segment of a car trip class Segment implements IValue{... Java compiler can now verify that a method 'value' with the required header is indeed defined in each of these classes. The final part of our contract is the requirement in the implementation of the 'totalValue' method that only instances of this.first for which this.first.value() is invoked are those that belong to classes that implement the 'value' metod. This is specified as follows: // in the class ConsLoObj: int totalValue(){ return ((IValue)this.first).value() + this.rest.totalValue(); } The term ((IValue)this.first) specifies that this.first must be of the type IValue. Once that is assured, we know that the method 'value' has been implemented for this.first and the rest of the computation can proceed safely. Notice that we used the word 'type'. An instance of a class has a type that is the combination of its class, all of its super classes, and of all the interfaces it implements. It defines all the fields that this instance can access, and all methods that this instance can invoke. We conclude with a complete working code for these classes and the list of objects, together with the interface IValue. */ /* +------------------+ | Interface IValue |<- - - - - - - + +------------------+ | | int value() | +------------------+ | +- - - - - - - - +- - - - - - + - -+ | | | +--------------+ +-------------+ +-------------+ | Picture | | Item | | Segment | +--------------+ +-------------+ +-------------+ | String title | | String name | | String from | | int width | | int pieces | | String to | | int height | | int price | | int miles | +--------------+ +-------------+ | int gallons | | int value() | | int value() | | int price | +--------------+ +-------------+ +-------------+ | int value() | +-------------+ +--------------------------+ | ALoObj |<--------------+ +--------------------------+ | |abstract int totalValue() | | +--------------------------+ | / \ | --- | | | ------------------- | | | | +------------------+ +------------------+ | | MTLoObj | | ConsLoObj | | +------------------+ +------------------+ | | int totalValue() | | Object first | | +------------------+ | ALoObj rest |---+ +------------------+ | int totalValue() | +------------------+ */ /* ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ;;;;; ; ;; ; ;;;; ; ; ; */ interface IValue{ // produce a value of this object int value(); } /* ; ; ; ; ;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ;;;; ; ; ; ; ;;; ; ; ; ; ; ; ; ; ; ;; ; ; ; ;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;; ;; ;; ; ; ;;;; ; ; ; */ // to represent a picture in a file directory class Picture implements IValue{ String title; int width; int height; Picture(String title, int width, int height) { this.title = title; this.width = width; this.height = height; } // compute the size of this picture int value(){ return this.width * this.height; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.title ... ... this.width ... ... this.height ... ... this.value() ... } */ } /* ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ;;; ; ;; ;; ; ; ; ; ; ;; ;; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ;;;; ; ; ; ; ; ; */ // to represent a salad item class Item implements IValue{ String name; int pieces; int price; Item(String name, int pieces, int price) { this.name = name; this.pieces = pieces; this.price = price; } // compute the cost of this salad ingredients int value(){ return this.pieces * this.price; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.name ... ... this.pieces ... ... this.price ... ... this.value() ... } */ } /* ; ; ; ; ;;;; ; ; ; ; ; ; ; ; ;;; ;; ; ; ;; ;; ;;; ; ;; ;;;; ; ;; ; ; ; ;; ;; ;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;;; ; ; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;;; ;;;; ;; ; ; ; ; ;;;; ; ; ;; ; ; ; ; ; ; ;;;; */ // to represent one segment of a road trip class Segment implements IValue{ String from; String to; int miles; int gallons; int price; Segment(String from, String to, int miles, int gallons, int price) { this.from = from; this.to = to; this.miles = miles; this.gallons = gallons; this.price = price; } int value(){ return this.miles; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.from ... ... this.to ... ... this.miles ... ... this.gallons ... ... this.price ... ... this.value() ... } */ } /* ; ; ; ; ; ; ;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;;;;; ;;; ;;;; ; ;; ; ; ; ; ; ; ;; */ // to represent a list of objects abstract class ALoObj { // count the number of elements in this list abstract int count(); // does this list contain the given object abstract boolean contains(Object obj); // compute the total value of all objects in this list abstract int totalValue(); /* // ** DRAFT TEMPLATE ** Edit as needed. // purpose statement abstract ??? mmm(); */ } /* ; ; ; ; ; ; ;;;;;;; ; ;;;; ; ; ; ;; ;; ; ; ; ; ; ; ;; ;; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;;;;;; ;;; ;;;; ; ;; ; ; ; ; ; ; ;; */ // to represent an empty list of objects class MTLoObj extends ALoObj { MTLoObj() {} int count() { return 0; } boolean contains(Object o){ return false; } int totalValue(){ return 0; } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.mmm() ... ... this.count() ... ... this.contains(Object o) ... ... this.value() ... } */ } /* ; ; ; ; ;;;; ; ;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;; ;;; ; ;;; ; ; ; ;; ; ; ; ; ; ;; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ;;;; ;;; ; ; ;;; ;;;;;; ;;; ;;;; ; ;; ; ; ; ; ; ; ;; */ // to represent a nonempty list of objects class ConsLoObj extends ALoObj { Object first; ALoObj rest; ConsLoObj(Object first, ALoObj rest) { this.first = first; this.rest = rest; } int count() { return 1 + this.rest.count(); } boolean contains(Object o){ return this.first.equals(o) || this.rest.contains(o); } int totalValue(){ return ((IValue)this.first).value() + this.rest.totalValue(); } /* // ** DRAFT TEMPLATE ** Edit as needed. ??? mmm() { ... this.first ... ... this.rest.mmm() ... ... this.count() ... ... this.contains(Object o) ... ... this.totalValue() ... } */ } /* ; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;; ;; ; ;; ; ;;; ;;; ; ;;;;; ; ; ; ; ;; ;; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ;;;; ; ; ; ; ; ; ;;;;;; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;;;;;; ; ;;;;; ; ; ; ; ;; ; ;;;; ;;; ; ; ; ; ; ; */ class Examples{ Examples() {} Picture snow = new Picture("Snow.gif", 400, 200); Picture beach = new Picture("Beach.gif", 200, 300); Picture bluff = new Picture("Bluff.gif", 200, 600); Item tom = new Item("tomato", 3, 50); Item pep = new Item("pepper", 2, 60); Item rad = new Item("raddish", 6, 10); Segment b2n = new Segment("Boston", "NYC", 200, 10, 210); Segment n2t = new Segment("NYC", "Trenton", 120, 5, 200); Segment t2p = new Segment("Trenton", "Philly", 80, 2, 180); ALoObj objPictures = new ConsLoObj(snow, new ConsLoObj(beach, new ConsLoObj(bluff, new MTLoObj()))); ALoObj objSalad = new ConsLoObj(tom, new ConsLoObj(pep, new ConsLoObj(rad, new MTLoObj()))); ALoObj objTrip = new ConsLoObj(b2n, new ConsLoObj(n2t, new ConsLoObj(t2p, new MTLoObj()))); // test the method count boolean testCount1 = objPictures.count() == 3; boolean testCount2 = objSalad.count() == 3; boolean testCount3 = objTrip.count() == 3; // test the method contains boolean testContains1 = objPictures.contains(snow) == true; boolean testContains2 = objPictures.contains(new Picture("Lake", 200, 200)) == false; boolean testContains3 = objSalad.contains(pep) == true; boolean testContains4 = objSalad.contains(new Item("Olive", 10, 10)) == false; boolean testContains5 = objTrip.contains(t2p) == true; boolean testContains6 = objTrip.contains(new Segment("DC", "Norfolk", 150, 8, 180)) == false; boolean testTotalValue1 = objPictures.totalValue() == 260000; boolean testTotalValue2 = objSalad.totalValue() == 330; boolean testTotalValue3 = objTrip.totalValue() == 400; }