Lab 6: Abstracting with Function Objects
Getting Started
Download the files in Lab6.zip. The folder contains the files:
ImageFile.java |
ILoIF.java |
MTLoIF.java |
ConsLoIF.java |
ExamplesImageFile.java |
Diagram.txt |
We give you partially defined classes and examples so you can quickly skip typing what you already know, and focus on the new material. If any of the pre-supplied code is unfamiliar to you, then be sure to read through it before beginning the rest of the lab!
Create a new project Lab6-FunctionObjects and import into it all of the given .java files. Also add tester.jar to the Java classpath.
Introduction – Tutorial with filter
Let’s start with three methods that deal with lists of files: filterSmallerThan40000, filterNamesShorterThan4, countSmallerThan40000. Each of them should do exactly what their names suggest.
Do Now!
Design these three methods on ILoIF. They should be very similar...
Look at the first two methods. They should only differ in the body of the conditional in the class ConsListImage. The two versions look like this:
if (this.first.size() < 40000) if (this.first.name.length() < 4)
Both represent a boolean expression that depends only on the value of this.first. Think about the filter loop function in DrRacket. Its contract and header were:
;; filter: (X -> boolean) [Listof X] -> [Listof X] ;; to construct a list from all those items ;; in alox for which p holds (define (filter p alox)...)
The argument p was a function/predicate that consumed an item from the list (for example the first) and produced a boolean value that indicated whether the item is acceptable.
Java does not allow us to use functions or methods as arguments. To get around this problem we need to go through several steps:
Define an interface that contains as its only method the header for the desired predicate: we define the interface ISelectImageFile:
// to represent a predicate for ImageFile-s interface ISelectImageFile{ // Return true if the given ImageFile // should be selected boolean apply(ImageFile f); }
Now any class that implements this interface will necessarily implement this predicate method. Suppose we try to design an abstracted filter method that consumes an object of the type ISelectImageFile as follows:
// produce a list of ImageFiles from this list // that satisfy the given predicate filter(ISelectImageFile pick);
Inside the implementations of filter (in ConsLoIF and MtLoIF) our template now includes
... pick.apply(ImageFile) ... -- boolean
Let’s hang on to that observation for a moment. In order to use it, we now need to define a class that implements this interface. Let’s implement a class that would be useful for filterSmallerThan40000: it needs to define the method apply that consumes an instance of ImageFile and returns true if the size of the given object is less than 40000. The following class definition accomplishes this task:
/* Select image files smaller than 40000 */ class SmallImageFile implements ISelectImageFile { /* Return true if the given ImageFile is smaller than 40000 */ public boolean apply(ImageFile f) { return f.height * f.width < 40000; } }
We haven’t implemented filter itself yet; we’re writing the tests first.
IListImageFile mtImagelist = new MTListImageFile(); IListImageFile imagelist = ..... IListImageFile smallImagelist = ... ISelectImageFile smallFiles = new SmallImageFile(); // test the method filter on small image files boolean testFilter(Tester t){ return t.checkExpect(mtImagelist.filter(this.smallFiles), this.mtImagelist) && t.checkExpect(imageList.filter(this.smallFiles), this.smallImagelist); }
Turning back to the implementations of filter, we observed above that we can compute
pick.apply(this.first)
If our pick object happens to be an instance of SmallImageFile, then this expression will select the ImageFile objects for which the size is smaller than 40000.
Do Now!
Which language mechanism of object-oriented programming ensures this for us?
Make sure you view the diagram using a fixed-width font.
Abstracting with Function Objects
The only purpose for defining the class SmallImageFile is to implement one method that determines whether the given ImageFile object has the desired property (a predicate method). An instance of this class can then be used as an argument to a method that deals with ImageFiles.
Complete the design of filter that produces a list of all ImageFiles that satisfy the ISelectImageFile predicate. If done correctly, it should look similar to, but be more general than, the example filter-like methods you started with. Test it with as many of your predicates as you can. (You will define some more below.)
In the ExamplesImageFile class, define the missing tests for the class SmallImageFile.
We now want to determine whether the name in the given ImageFile object is shorter than 4.
Design the class NameShorterThan4 that implements the ISelectImageFile interface with an appropriate predicate method.
Make sure in the class ExamplesImageFile you define an instance of this class and test the method.
Design the class GivenKind that implements the ISelectImageFile interface with a method that produces true for all ImageFiles that are of the given kind. The desired kind is given as a parameter to the constructor, and so is specified when a new instance of the class GivenKind is created.
Hint: Add a field to represent the desired kind to the class GivenKind.
Design the method allSmallerThan40000 that determines whether all items in a list are smaller that 40000 pixels. The method should take an instance of the class SmallImageFile as an argument.
Design the method allNamesShorterThan4 that determines whether all items in a list have a name that is shorter than 4 characters. The method should take an instance of the class NameShorterThan4 as an argument.
Design the method allSuchImageFile that that determines whether all items in a list satisfy the predicate defined by the apply method of a given instance of the type ISelectImageFile.
Note: This resembles the andmap function in DrRacket. In the ExamplesImageFile class test this method by abstracting over the method allSmallerThan40000 and the method allNamesShorterThan4.
In the ExamplesImageFile class use the method allSuchImageFile and the class GivenKind to determine whether all files in a list are jpg files. This should be written as a test case for the method allSuchImageFile.
Do it again, but now ask about the gif files.
Follow the same steps as above to design the method anySuchImageFile that that determines whether there is an item a list that satisfies the predicate defined by the apply method of a given instance of the type ISelectImageFile.
Finish the work at home.
Exercise
Food for thought: How would this program be different if we have instead worked with lists of Books? What about lists of IShapes?
Additional practice
Suppose you want to sort the image files by size, or perhaps by their names. How could you do it writing only one sort method?
Hint: Design the interface IImageFileComparator that defines a method compare that consumes two ImageFiles and produces an integer result, just as we did in class for marathon runners. Remember, the result value should be less that zero if the first item is comes before the second in our ordering, produces a zero if the two items are equal in our ordering, and prodces a positive integer if the second item comes before the first one in our ordering.
Of course, you now need to define a class that represent the ordering by the image size (name it ImageFileSizeComparator), and another class that compares the image files by their names (name this class ImageFileNameComparator).
You are now ready to design the sort method that produces a sorted list of ImageFiles based on the desired ordering.
Finish this method design —
make sure you test all the methods.