|
home—info—archive—exams—lectures—labs—hws
Recipe—Laws—lies—syntax—java.lang docs—java.util docs
…continuing our discussion on inheritance and interfaces.
How to fake multiple inheritance, if Java doesn't allow it? That is, suppose we have Guard Dogs, which are like Dogs but they have some additional guard behaviours:
String threatenSound(); // How does this guard threaten? boolean deters(Animal anAnimal); // Does this guard succesfully deter anAnimal? |
But now we have the problem, that for a class GuardDog we want to Extend two different classes.
class GuardDog2 extends Dog implements IGuard { private Guard guardDelegate = new Guard(); public String threatenSound() { return this.guardDelegate.threatenSound(); } public boolean deters( Animal a ) { return this.guardDelegate.deters(a); } GuardDog2() { super("Rex"); } } |
This is better, but not a perfect solution. For instance: And, if we want to make a SmallGuardDog, what class should it extend — GuardDog, or SmallDog? Either answer is unsatisfactory. Delegate pattern can still help, though it's starting to become cumbersome: for every class we can have an interface, and then have SmallGuardDog extend SmallDog implement IGuardDog2, and it will have a GuardDog2 delegate instance inside of it. This avoids repeated code (at the cost of our mental overhead).
In lab14a—home photoshopping network: arrays of pixels, we saw that we might want to write many many ways of modifying an image. Each method does specific processing of individual pixels, but each of those methods probably has repeated code: a loop to process an entire row of pixels, and another loop to process all the rows of the entire image.
We'd like to avoid that repetition, and only write the do-one-row and do-entire-image loops once. In fact, we'd like to say
Hey image -- I'll pass you a method which processes one single pixel, and I want you to apply this method to every pixel you have.Unfortunately, Java does not let us pass around methods. But here's how we can fake it, using the command pattern:
class MyEffects { //... other methods omitted /** Process an entire image by giving each of our pixels to a PixelProcessor * to be worked on. * @param p A particular PixelProcessor object. */ public void doAll( PixelProcessor p ) { // Call p.doPixel on each pixel of data[][]. // See below for the full code. } |
import java.awt.Color; /** An interface with a single method: process one pixel of an image. */ interface PixelProcessor { /** Process one particular pixel in an image. * @param r The row of `data` our pixel is in. * @param c The column of `data` our pixel is in. * @param data The array of pixels; we may change the contents of data[][] -- * in particular, we'll probably modify data[r][c] but nothing else. */ void doPixel( int r, int c, Color[][] data ); } |
/** A PixelProcessor which removes everything but the redness * from a pixel. */ class RedFilterer implements PixelProcessor { void doPixel( int r, int c, Color[][] data ) { Color k = data[r][c]; int existingRed= k.getRed(); data[r][c] = new Color( existingRed, 0, 0 ); } |
/** A PixelProcessor which converts the pixel to black-and-white. */ class BWConverter implements PixelProcessor { void doPixel( int r, int c, Color[][] data ) { Color k = data[r][c]; int brightness = (k.getRed() + k.getBlue() + k.getGreen()) / 3; data[r][c] = new Color( brightness, brightness, brightness ); } |
/** A PixelProcessor which makes a pixel bright if it differs greatly * from its neighbor to the right or the neighbor below. */ class EdgeDetector implements PixelProcessor { void doPixel( int r, int c, Color[][] data ) { Color here = data[r][c]; Color below = (r+1 >= data.length) ? data[r+1][c] : here; Color right = (c+1 >= data[0].length) ? data[r][c+1] : here; // If we are at the edge, consider our neighbor to be ourself. // This works okay, because when we take the difference between // self and neighbor, we'll get 0, which is a reasonable notion of 'difference'. int newRed = Math.max( Math.abs( here.getRed() - below.getRed() ), Math.abs( here.getRed() - right.getRed() ) ); int newGreen = Math.max( Math.abs( here.getGreen() - below.getGreen() ), Math.abs( here.getGreen() - right.getGreen() ) ); int newBlue = Math.max( Math.abs( here.getBlue() - below.getBlue() ), Math.abs( here.getBlue() - right.getBlue() ) ); data[r][c] = new Color( newRed, newGreen, newBlue ); } /* BUG: this code is relying on the fact that the pixels below and to the right * are the *original* image (not already modifed for their edges). * This happens to be true, but *only* because MyEffects happens to * work top-to-bottom, left-to-right. * * In fact, we realize that some special effects shouldn't modify the image * they're working with; they should leave it untouched so future pixels * also know what the original picture was. * So instead of modifying the current pixel data[r][c], they should instead * be creating pixel r,c of a *new* picture (a new Color[][]). * Perhaps two arrays should be passed in to doPixel: * the original, unchanged source, and a new array which we're filling. * This modification to the architecture is left to the reader. */ } |
// Inside class MyEffects public void doAll( PixelProcessor p ) { for (int r= 0; r < data.length; ++r ) { doRow(p, r); } } /** Process one row of an image * by giving each pixel in the row to the PixelProcessor p. * (modifying the contents of data[][]) * @param rowNum the row of data to process. */ private void doRow( PixelProcessor p, int rowNum) { for (int c= 0; c < data[rowNum].length; ++c ) { p.doPixel( rowNum, c, data ); } |
this.doAll( new EdgeDetector() ) this.doAll( new RedFilter() ) |
What has all this gained us?
Note that the compareTo interface is (nearly) an instance of the command pattern: instead of passing in a method compareTo as an argument, and instead of passing an object which contains the compareTo method, we make sure that the object in question already has a compareTo method inside of it.
home—info—archive—exams—lectures—labs—hws
Recipe—Laws—lies—syntax—java.lang docs—java.util docs
©2008, Ian Barland, Radford University Last modified 2008.Apr.25 (Fri) |
Please mail any suggestions (incl. typos, broken links) to ibarlandradford.edu |