TSRJ Workshop 2008 - Advanced track Tuesday morning lecture ------------------------ Goals: Show how to convert data definitions in Scheme to class hierarchy definitions in Java. We want to make it clear that they have been designing class based programs for three days already - they just did not know it. We also want to show that understanding data helps you in designing class hierarchies and make the program 'object-driven'. Select an example that has the following properties: -- illustrates a simple class with three fields of at least two different primitive types (e.g. Book with title, author, and price or year of publication or a catalog number) -- has one field that can be expanded to represent another simple class (e.g. Author instead of just name consists of name and year of birth) -- there are related classes of data that can be combined into a union (e.g. LibItem - that can be either a book, or a CD or a magazine) these should have some common fields (title, catalog number) and some that are different (CD has title, artist, and catalog number, Magazine has title, year, issue, and catalog number) -- a list of these items is something we may want to look at (list of books, a library catalog) The lecture is structured as follows: -------------------------------------------------------------------------------------------------------- Part 1: A Simple class -- class Book Part 2: Object containment -- class Book with class Author Part 3: Unions -- classes Book, CD, Magazine -> a LibItem union Part 4: Self-referential data -- a list of Book, list of LibItem -- let them discover how to do it -------------------------------------------------------------------------------------------------------- Each part starts with the appropriate Scheme data definition, is converted to a class diagram, including examples of data. It is then converted to the Java code - gradually adding ProfessorJ convenience features: in Part 1 we show the Examples class and the Interactions window in Part 4 we let the participants see that combining union with object containment leads to self-reference - using the same arrows we have seen in Scheme and examples are shown in Java as well. Watch out for the following: If we define ALibItem alice = new Book("Alice", "LC", 234); we cannot then ask to see its author as in alice.autor because even though this particular LibItem is a Book, it could have been a CD or a Magazine and Java does not know until the runtime. It is best not to even get close to raising this issue. Notes: -- Choose names for the classes of data that convert well from Scheme to Java - show them that the hyphenated names from Scheme convert to firstCap style in Java. Mention the convention to start the names of datatypes with capitals, and the name of data objects, fields, and methods in lower case. (Later we add constants as ALLCAPS). -- Remember to add a comment to each data definition explaining what does the class of data represent. -------------------------------------------------------------------------------------------------------- Part 1: A Simple class -------------------------------------------------------------------------------------------------------- Start with a simple example - provide the data definition in Scheme. ;; to represent a book in the library ;; A Book is (make-book String String Number) (define-struct book (title author cnum)) ;; examples (define alice (make-book "Alice" "LC" 234)) (define glass (make-book "TTLG" "LC" 345)) (define cathat (make-book "Cat in a Hat" "Seuss" 777)) Extract the relevant information from the data definition and make a class diagram: +---------------+ | Book | +---------------+ | String title | | String author | | Num cnum | +---------------+ This is all thee relevant information needed to define a data that represents it. Java uses the same information, same atomic pieces of data, combines them into a class definition: // to represent a book in the library class Book{ String title; String author; int cnum; } Mention numbers in Java - no big deal, we just have to do it. Stay with int-s and double-s only. Now, in Scheme we got three types of functions - a constructor, a selector for each field and a predicate. Recall: we used the constructors already. The selectors were: (book-title alice) (book-author cathat) Let's put aside the predicates for now. In Java - we have to add the constructor: Book(String title, String author, int cnum){ this.title = title; this.author = author; this.cnum = cnum; } What is 'this'? -- it is a way to refer to the fields defined in 'this' class and the methods defined in 'this' class. The format of the constructor when written this way makes it clear to the student what piece of data each of the names represent. ProfessorJ at the beginner level 'requires' the use of 'this' --- it is really helpful. The selectors are built in. Within the class definition we reference each field as this.fieldname - as we have seen in the constructor already. Let us make examples of data to see how the selectors work. Where to the data examples go? All programs in Java consist of a collection of classes. So, our book class contains all data definitions - and later programs - that deal with book data. The program that makes use of the Book data definitions has to be also a part of a class. We think of it as the client class and call it Examples class. (Any other name would do - this is just what we have been using.) So, here are the examples translated into Java: // the client class for our class hierarchy class Examples{ Examples(){} Book alice = new Book("Alice", "LC", 234); Book glass = new Book("TTLG", "LC", 345); Book cathat = new Book("Cat in a Hat", "Seuss", 777); } Run this in ProfessorJ. It will show all the fields in the Examples class in the Interactions window. Now explain the constructor for the Examples class. All fields are defined once and for all as the part of the class definition, and so every instance of the Examples class will have the same fields with the same values. Therefore the constructor takes no arguments and does nothing -- but it has to be provided. Now use the Interactions window to explore: > Examples e = new Examples(); > e.alice Book( title = "Alice", author = "LC", cnum = 234) > e.alice.title "Alice" > e.cathat.author "Seuss" > So this is how we use the selectors with data objects defined by the client class. -------------------------------------------------------------------------------------------------------- Part 2: Object containment -------------------------------------------------------------------------------------------------------- Here is a different data definition for books: ;; to represent a book in the library ;; A Book is (make-book String Author Number) (define-struct book (title author cnum)) ;; to represent an author of a book ;; An Author is (make-author String Number) (define-struct author (name dob)) ;; examples (define lc (make-author "LC" 1832)) (define drs (make-author "Seuss", 1915)) (define alice (make-book "Alice" lc 234)) (define glass (make-book "TTLG" lc 345)) (define cathat (make-book "Cat in a Hat" drs 777)) Extract the relevant information from the data definition and make a class diagram: +---------------+ | Book | +---------------+ | String title | | Author author |---------+ | int no | | +---------------+ v +-------------+ | Author | +-------------+ | String name | | int year | +-------------+ The arrow shows that the value of the field in the class Book is an object in the class Author. We call this 'object containment' and the arrow is called 'object containment arrow'. Start a new DrScheme Tab. It is easy to see how this will convert to Java. The process is automatic and tedious. Our students a few years ago suggested that they write a Scheme program to generate the Strings that represent the corresponding Java code - and so now we have a 'wizard' to do so: Use the 'InsertJavaClass' wizard to generate the code. Make sure you check the 'add class diagram' checkbox. Do not use the 'add toString' checkbox. You need to do it for both the Book class and the Author class - the object containment arrow has to be added by hand. Make the examples and run them. Author lc = new Author("Lewis Carroll", 1832); Author drs = new Author("Dr Seuss", 1915); Book2 alice = new Book2("Alice", this.lc, 234); Book2 glass = new Book2("TTLG", this.lc, 345); Book2 cathat = new Book2("Cat in a Hat",this.drs, 777); Notice that we refer to the objects defined in this (Examples) class with the prefix 'this.' -------------------------------------------------------------------------------------------------------- Part 3: Unions -------------------------------------------------------------------------------------------------------- Well, out library has also CDs and magazines. We may have the following Scheme data definitions: ;; to represent a library item ;; A LibItem is one of ;; -- Book ;; -- CD ;; -- Magazine ;; to represent a book in the library ;; A Book is (make-book String Number) (define-struct book (title author cnum)) ;; to represent a CD in the library ;; A CD is (make-cd String String NUmber Number) (define-struct cd (title artist tracks cnum)) ;; to represent a magazine in the library ;; A Magazine is (make-mag String Number Number Number (define-struct mag (title year issue cnum)) and we could make examples of the information shown above represented as data: (define cathat (make-book "Cat in a Hat" "Dr Seuss" 777)) (define alice (make-book "Alice" "Lewis Carroll" 234)) (define thriller (make-cd "Tapestry" "Carole King" 10 989)) (define pearl (make-cd "Pearl" "Janis Joplin" 12 858)) (define time (make-mag "Time" 2004 52 333)) (define wired (make-mag "Wired" 2004 3 444)) We already know how to define a class of data for each of the three parts of the union. Draw the diagrams for each of the three classes. +---------------+ +---------------+ +--------------+ | Book | | CD | | Magazine | +---------------+ +---------------+ +--------------+ | String title | | String title | | String title | | String author | | String artist | | int year | | int cnum | | int tracks | | int issue | +---------------+ | int cnum | | int cnum | +---------------+ +--------------+ Now add the box for LibItem and the 'union' arrow (ends with an open triangle): +---------+ | LibItem | +---------+ +---------+ | / \ --- | ------------------------------------------ | | | +---------------+ +---------------+ +--------------+ | Book | | CD | | Magazine | +---------------+ +---------------+ +--------------+ | String title | | String title | | String title | | String author | | String artist | | int year | | int cnum | | int tracks | | int issue | +---------------+ | int cnum | | int cnum | +---------------+ +--------------+ Two new Java terms are added to represent this relationship: The union gets a name that is the name of the common type of data: interface LibItem{} and each of the classes in the union declares that it is a part of this union: class Book implements LibItem{ ...} class CD implements LibItem{ ...} class Magazine implements LibItem{ ...} This is even more tedious to write - so, we have a better wizard 'Insert Java Union': // to represent a library item interface LibItem { } // to represent a book in a library class Book3 implements LibItem { String title; String author; int cnum; Book3(String title, String author, int cnum) { this.title = title; this.author = author; this. cnum = cnum; } } // to represent a CD in a library class CD implements LibItem { String title; String artist; int tracks; int cnum; CD(String title, String artist, int tracks, int cnum) { this.title = title; this.artist = artist; this.tracks = tracks; this. cnum = cnum; } } // to represent a magazine in a library class Magazine implements LibItem { String title; int year; int issue; int cnum; Magazine(String title, int year, int issue, int cnum) { this.title = title; this. year = year; this.issue = issue; this. cnum = cnum; } } Finally, we need examples: class Examples{ Examples(){} LibItem cathat = new Book3("Cat in a Hat", "Dr Seuss", 777); LibItem alice = new Book3("Alice", "Lewis Carroll", 234); LibItem thriller = new CD("Tapestry", "Carole King", 10, 989); LibItem pearl = new CD("Pearl", "Janis Joplin", 12, 858); LibItem time = new Magazine("Time", 2004, 52, 333); LibItem wired = new Magazine("Wired", 2004, 3, 444); } -------------------------------------------------------------------------------------------------------- Part 4: Self-referential data -------------------------------------------------------------------------------------------------------- At this point if there is time left let them 'discover' how to define a list of Book or a list of LibItem. Again, show the Scheme data definition, then identify the classes in the union, the object containment, draw the class diagrams and follow the arrows. Do a simple list of Book only - not LibItem-s: /* +--------+ | LoBook |<--------------+ +--------+ | +--------+ | | | / \ | --- | | | ------------------- | | | | +----------+ +-------------+ | | MTLoBook | | ConsLoBook | | +----------+ +-------------+ | +----------+ +-| Book first | | | | LoBook rest |-+ | | +-------------+ | | | | | v +--+ +---------------+ | Book | +---------------+ | String title | | String author | | Num cnum | +---------------+ */ // to represent a book in a library class Book{ String title; String author; int cnum; Book(String title, String author, int cnum){ this.title = title; this.author = author; this.cnum = cnum; } } // to represent a list of books interface LoBook { } // to represent an empty list of books class MTLoBook implements LoBook { MTLoBook() { } } // to represent a nonempty list of books class ConsLoBook implements LoBook { Book first; LoBook rest; ConsLoBook(Book first, LoBook rest) { this.first = first; this.rest = rest; } } /* And, again, we need examples. */ class Examples3{ Examples3(){} Book ch = new Book("Cat in a Hat", "Dr Seuss", 777); Book alice = new Book("Alice's Adventures in Wonderland", "Lewis Carroll", 234); Book geh = new Book("Green Eggs and Ham", "Dr Seuss", 779); LoBook mtbks = new MTLoBook(); LoBook lobks1 = new ConsLoBook(this.ch, this.mtbks); LoBook lobks2 = new ConsLoBook(this.alice, this.lobks1); LoBook lobks3 = new ConsLoBook(this.geh, this.lobks2); }