/* The Carcassonne Game Players create a map from a heap of tiles. Each tile displays a number of things, including castles, roads, abbeys, and grass. A software version of a player needs a way to refer to a specific tile in the map. Let's use Coordinates for that, aka Posns. Finally, on each tile there are positions on which players can place followers. To make things simple, let's assume each tile contains exactly one follower. The first step: translate this information into a data representation */ /* +--------------------+ | Tile | +--------------------+ | Posn p | | Follower f | | String description | +--------------------+ +--------------+ | Follower | +--------------+ | String color | | String name | +--------------+ +-------+ | Posn | +-------+ | int x | | int y | +-------+ */ // a tile in a Carcassonne map class Tile { Posn p; Follower f; String description; Tile(Posn p, Follower f, String description) { this.p = p; this.f = f; this.description = description; } } // a Carcassonne follower class Follower { String color; String name; Follower(String color, String name) { this.color = color; this.name = name; } } // Cartesian coordinates class Posn { int x; int y; Posn(int x, int y) { this.x = x; this.y = y; } } /* Next we need to figure out some of tasks that players and game administrators have to perform on the tiles of the map. Today we deal with these two: -- given a list of tiles, extract the list of Followers -- given a list of tiles, extract the list of Posns Naturally, this suggests we need to formulate data definitions (aka, uml diagrams) for three more classes: -- list of Tiles -- list of Followers -- list of Posns */ /* +-------+ | LTile |<-----------+ +-------+ | +-------+ | / \ | --- | | | --------------- | | | | +------------+ +----+ | | TCons | | Mt | | +------------+ +----+ | | Tile first | +----+ | | LTile rest |-+ | +------------+ | | | | +------------+ */ abstract class LTile { } class TCons extends LTile { Tile first; LTile rest; TCons(Tile first, LTile rest) { this.first = first; this.rest = rest; } } class MtT extends LTile { MtT() { } } /* +------------+ | LFollowers |<-----------+ +------------+ | +------------+ | / \ | --- | | | ------------------ | | | | +-----------------+ +-----+ | | FCons | | FMt | | +-----------------+ +-----+ | | Follower first | +-----+ | | LFollowers rest |-+ | +-----------------+ | | | | +-------------+ */ abstract class LFollowers { } class FCons extends LFollowers { Follower first; LFollowers rest; FCons(Follower first, LFollowers rest) { this.first = first; this.rest = rest; } } class FMt extends LFollowers { FMt() { } } /* +------------+ | LPosns |<-----------+ +------------+ | +------------+ | / \ | --- | | | ------------------ | | | | +-----------------+ +-----+ | | PCons | | PMt | | +-----------------+ +-----+ | | Posn first | +-----+ | | LPosns rest |-+ | +-----------------+ | | | | +-------------+ */ abstract class LPosns { } class PCons extends LPosns { Posn first; LPosns rest; PCons(Posn first, LPosns rest) { this.first = first; this.rest = rest; } } class PMt extends LPosns { PMt() { } } /* Well, even a blind woman can see that this is copy-and-paste code and we all know that this is dangerous. So let's do better than this. Fortunately, we have taken CSU 211 and know how to use the design recipe for abstraction: -- circle the differences -- use variables instead -- add the variables to the parameter list of the function In this case, the function consumes a Java type and produces a UML diagram or in reality an entire bunch of classes: */ /* ;; JavaType -> UML ;; create a generic list of _type_ (define (make-list type) +------------+ | LType |<-----------+ +------------+ | +------------+ | / \ | --- | | | ------------------ | | | | +-----------------+ +-----+ | | TCons | | TMt | | +-----------------+ +-----+ | | type first | +-----+ | | LType rest |-+ | +-----------------+ | | | | +-------------+ ) The key thing to note is that >>>type<<< shows up in the second box (TCons). Okay so this is _not_ Java as we know it and hate it. Instead, it is the new and improved Java 1.5. [Note: software companies number products. The claim is that each release removes errors and problems. Instead, we all know that it adds new features and thus introduces new bugs. So -- the number N really stands for the N million bugs not found yet.] */ /* What are we to do? We use the next best thing -- Object, the most general type in Java. We know that everything is an object so we make a list of objects and hope for the best. +------------+ | LObject |<-----------+ +------------+ | +------------+ | / \ | --- | | | ------------------ | | | | +-----------------+ +-----+ | | OCons | | OMt | | +-----------------+ +-----+ | | Object first | +-----+ | | LObject rest |-+ | +-----------------+ | | | | +-------------+ */ abstract class LObjects { } class ConsO extends LObjects { Object first; LObjects rest; ConsO(Object first, LObjects rest) { this.first = first; this.rest = rest; } } class Mt extends LObjects { Mt() { } } // Okay, time to make examples: class Examples { LObjects lot = // a list of tiles new ConsO(new Tile(new Posn(1,2),new Follower("green","mf"),"castle"), new ConsO(new Tile(new Posn(7,8),new Follower("red","mf"),"castle"), new ConsO(new Tile(new Posn(-1,2),new Follower("red","jc"),"road"), new Mt()))); LObjects lof = // a list of followers new ConsO(new Follower("green","mf"), new ConsO(new Follower("red","mf"), new ConsO(new Follower("red","jc"), new Mt()))); LObjects lop = // a list of posns new ConsO(new Posn(1,2), new ConsO(new Posn(7,8), new ConsO(new Posn(-1,2), new Mt()))); } /* If lot is given, we know that -- lot.getFollowers() // should produce lof -- lot.getPosn() // should produce lop So let's add those methods to the list of objects: LObject: // get the list of followers from this list of tiles abstract LObject getFololowers() Mt: LObject getFollowers() { return this; // which is the empty list } ConsO: LObject getFollowers() { return new ConsO( ((Tile)this.first).f, this.rest.getFollowers() ); } And the same for getPosn. Of course, putting the cast to Tile in the middle of a class concerning a list of generic objects defeats the purpose of the idea of abstraction. We need to come up with something better than that. Ken's proposal: let's write a generic get method: LObject: // get something from each item on this list abstract LObject get() Mt: LObject get() { return this; // which is the empty list } ConsO: LObject get() { return new ConsO( ... this.first ... this.rest.getFollowers() ); } Hah, problem is we don't know what to do with this.first. Plus, we really want to do different things with this.first at different times. In Scheme, we would have just passed in a function that extracts the right piece from this.first. Unfortunately, Java doesn't have functions. It has ints, doubles, booleans, Strings, which are useless here, and objects. So, let's say we have a class GetSomething and get receives an instance of this class: LObject: // get something from each item on this list abstract LObject get(GetSomething x) Mt: LObject get(GetSomething x) { return this; // which is the empty list } ConsO: LObject get(GetSomething x) { return new ConsO( ... x ... this.first ... this.rest.getFollowers(x) ); } Well we can't call objects but if they contain a method, we can call those methods. So assume that the class GetSomething contains the method Object poke(Object o) { ... } Now we can complete ConsO in version 2 of ConsO: LObject get(GetSomething x) { return new ConsO( x.poke(this.first), this.rest.getFollowers(x) ); } */ abstract class LObjects_1 { // get something from each item on this list abstract LObjects_1 get(GetSomething x); } class ConsO_1 extends LObjects_1 { Object first; LObjects_1 rest; ConsO_1(Object first, LObjects_1 rest) { this.first = first; this.rest = rest; } LObjects_1 get(GetSomething x) { return new ConsO_1( x.poke(this.first), this.rest.get(x) ); } } class Mt_1 extends LObjects_1 { Mt_1() { } LObjects_1 get(GetSomething x) { return this; // which is the empty list } } class GetSomething { // extract a follower from an object tile Object poke(Object o) { return ((Tile)o).f; } } class Examples_1 { LObjects_1 lot = // a list of tiles new ConsO_1(new Tile(new Posn(1,2),new Follower("green","mf"),"castle"), new ConsO_1(new Tile(new Posn(7,8),new Follower("red","mf"),"castle"), new ConsO_1(new Tile(new Posn(-1,2),new Follower("red","jc"),"road"), new Mt_1()))); LObjects_1 lof = // a list of followers new ConsO_1(new Follower("green","mf"), new ConsO_1(new Follower("red","mf"), new ConsO_1(new Follower("red","jc"), new Mt_1()))); LObjects_1 lop = // a list of posns new ConsO_1(new Posn(1,2), new ConsO_1(new Posn(7,8), new ConsO_1(new Posn(-1,2), new Mt_1()))); LObjects_1 lof_computed = // use the get method now: lot.get(new GetSomething()); } /* Great. Evaluate new Examples_1() and you see that lof and lof_computed are the same list. But how are we going to extract the coordinates (posns) form this list now? We need to have a _different_ class GetSomething whose poke method behaves very differently. And indeed, we need a different class GetSomething with a different poke for everything we want to compute from a list of objects. That doesn't work. What we do instead we construct a minimalistic description of what get needs to consume -- an interface that describes poke. And then we define classes with real poke methods that have that signature. */ interface IGetSomething { Object poke(Object o); } class GetFollower implements IGetSomething { // extract a follower from an object tile Object poke(Object o) { return ((Tile)o).f; } } class GetPosn implements IGetSomething { // extract a follower from an object tile Object poke(Object o) { return ((Tile)o).p; } } // ----------------------------------------------------------------------------- // NOT COVERED /* Of course an interface is really just an abstract class with absolutely no methods, and with no variable fields. 'implements' means that the class overrides all of the interface's methods with concrete methods. */ // Here is how everything works: abstract class LObjects_2 { // get something from each item on this list abstract LObjects_2 get(IGetSomething x); } class ConsO_2 extends LObjects_2 { Object first; LObjects_2 rest; ConsO_2(Object first, LObjects_2 rest) { this.first = first; this.rest = rest; } LObjects_2 get(IGetSomething x) { return new ConsO_2( x.poke(this.first), this.rest.get(x) ); } } class Mt_2 extends LObjects_2 { Mt_2() { } LObjects_2 get(IGetSomething x) { return this; // which is the empty list } } class Examples_2 { LObjects_2 lot = // a list of tiles new ConsO_2(new Tile(new Posn(1,2),new Follower("green","mf"),"castle"), new ConsO_2(new Tile(new Posn(7,8),new Follower("red","mf"),"castle"), new ConsO_2(new Tile(new Posn(-1,2),new Follower("red","jc"),"road"), new Mt_2()))); LObjects_2 lof = // a list of followers new ConsO_2(new Follower("green","mf"), new ConsO_2(new Follower("red","mf"), new ConsO_2(new Follower("red","jc"), new Mt_2()))); LObjects_2 lop = // a list of posns new ConsO_2(new Posn(1,2), new ConsO_2(new Posn(7,8), new ConsO_2(new Posn(-1,2), new Mt_2()))); LObjects_2 lof_computed = // use the get method now: lot.get(new GetFollower()); LObjects_2 lop_computed = lot.get(new GetPosn()); } // evaluate new Examples_2() and compare the lists