/* --- CSU213 Spring 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 11: Don't be a Copy Cat! Goals: - Learning to design abstract classes and methods within them Introduction: Looking at the following data definition (represented as a class diagram) it is clear that there is a great deal of -- we hope unnecessary -- repetition. The design recipe for abstractions tells us to identify the commonalities and abstract over them. This may look like extra work, but sometimes it is actually necessary. +---------+ | LibItem | +---------+ / \ | - - - - - - - - - - - - - - - - - - - - - - | | | +---------------+ +---------------+ +--------------+ | Book | | CD | | Magazine | +---------------+ +---------------+ +--------------+ | int catNo | | int catNo | | int catNo | | String title | | String title | | String title | | int dueDate | | int dueDate | | int dueDate | | String author | | String artist | | int vol | +---------------+ +---------------+ | int no | +--------------+ Consider the following problem. You are trying to sort the library items by their catalog number. You need to be able to compare two library items and detgermine whether the catalog number of one of then is smaller than the catalog number of the other one. This calls for the following purpose statement and method header: // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){...} However, we have no instances of LibItems. So, we add the method to every class that implements LIbItem interface. In the class Book we look at the template: ... this.catNo ... -- int ... this.title ... -- String ... this.dueDate ... -- int ... this.author ... -- String and, of course, we have ... that ..., but the only thing we know about it is that it is a LibItem. We could use the visitor pattern we have used when comparing shapes in the lab and add a method with the header // does this library item has a catalog number greater than the given number? boolean greaterCatNo(int catNo) and then the body of the original method would be: // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){ return that.greaterCatNo(this.catNo); } but both of these methods would have to appear in all classes that implement the LibItem interface!!!!! Instead, we woould like to indicate that the LIbItem classes have something more in common, namely fields --- and possibly some methods as well. To do this we use 'inheritance' - a common super class that is extended by several subclasses. We also say that the subclasses extend the super class. The super class can declare fields and methods, just like any other class. If all methods can be defined in the super class, it becomes a class just like any other. So, we can modify the class definitions as follows: +--------------+ | LibItem | +--------------+ | int catNo | | String title | | int dueDate | +--------------+ / \ --- | ---------------------------------------- | | | +---------------+ +---------------+ +----------+ | Book | | CD | | Magazine | +---------------+ +---------------+ +----------+ | String author | | String artist | | int vol | +---------------+ +---------------+ | int no | +----------+ We change the shape of the arrows - the inheritance arrow in the official UML is shown as a solid line with an open triangle at the end, as opposed to the implementation arrow that is made of dashed lines and an open arrow. The classes are now defined as follows: // to represent a library item abstract class LibItem { int catNo; String title; int dueDate; } // to represent a book in a library class Book extends LibItem { String author; Book(int catNo, String title, int dueDate, String author) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; this.author = author; } } // to represent a CD in a library class CD extends LibItem { String artist; CD(int catNo, String title, int dueDate, String artist) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; this.artist = artist; } } // to represent a magazine in a library class Magazine extends LibItem { int vol; int no; Magazine(int catNo, String title, int dueDate, int vol, int no) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; this.vol = vol; this.no = no; } } --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- Alternately, we can lift into the abstract class most of the work done by the constructor: // to represent a library item abstract class LibItem { int catNo; String title; int dueDate; LibItem(int catNo, String title, int dueDate) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; } } // to represent a book in a library class Book extends LibItem { String author; Book(int catNo, String title, int dueDate, String author) { super(catNo, title, dueDate); this.author = author; } } // to represent a CD in a library class CD extends LibItem { String artist; CD(int catNo, String title, int dueDate, String artist) { super(catNo, title, dueDate); this.artist = artist; } } // to represent a magazine in a library class Magazine extends LibItem { int vol; int no; Magazine(int catNo, String title, int dueDate, int vol, int no) { super(catNo, title, dueDate); this.vol = vol; this.no = no; } } --------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- The constructor in the classes Book, CD, and Magazine first calls the 'super' constructor and initializes the three common fields, then proceeds to initialize the remaining fields. The super constructor call --must be-- the first line in the body of the constructor in the subclass. We now continue to desing the method smallerThan. Recall the method header: // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){...} Some examples are: Book bk = new Book(123, "Beaches", 45, "PC"); CD cd = new CD(234, "Pearl", 40, "JJ"); Magazine mw = new Magazine(143, "MW", 20, 4); bk.smallerThan(cd) ---> should be true mw.smallerThan(bk) ---> should be false In the LibItem class we have the following template: ... this.catNo ... -- int ... this.title ... -- String ... this.dueDate ... -- int ... that.catNo ... -- int ... that.title ... -- String ... that.dueDate ... -- int The rest is easy: // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){ return this.catNo < that.catNo; } --- and, of course we run the tests. We can similarly commpute the number of days an item os overdue. We let you to work on the design of the following method: // produce the number of days this book is overdue on the given day // remember, this cannot be less than zero int daysOverdue(int today){...} Note, that we decided to use a single integer to represent the date as the number of days since the library opened - to eliminate the worries about months and days and years. We now want the method that computes the fine, based on the number of days an item is overdue. Here are the specifications: -- we charge 10 cents per day for each book, but at most $20 -- we charge 50 cents per day for each CD, but at most $20 -- we charge 30 cents per day for each magazine, and there is no limit It is clear we need three different methods, one for each class. In the LibItem class we can only include the method header. To indicate that there is no method body followint the header, we declare the method to be 'abstract'. The net effect is similar to what we had seen with interfaces - it is a contract mandating that every class that extends this super class must either define this method, or declare it as abstract and be an abstract class. Any class that contains an abstract method must be abstract. Here is the LibItem class: // to represent a library item abstract class LibItem { int catNo; String title; int dueDate; LibItem(int catNo, String title, int dueDate) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; } // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){ return this.catNo < that.catNo; } // compute the fine for this library item on the given day abstract int fine(int today); } The methods in the three subclasses can then be designed by follwing the design recipe. Complete working code is included below. */ // to represent a library item abstract class LibItem { int catNo; String title; int dueDate; LibItem(int catNo, String title, int dueDate) { this.catNo = catNo; this.title = title; this.dueDate = dueDate; } // is the catalog number of this item lower that the catalog number // of the given item? boolean smallerThan(LibItem that){ return this.catNo < that.catNo; } // compute the number of days this library item is overdue int daysOverdue(int today){ return Math.max(0, today - this.dueDate); } } // to represent a book in a library class Book extends LibItem { String author; Book(int catNo, String title, int dueDate, String author) { super(catNo, title, dueDate); this.author = author; } int fine(int today){ return Math.min(2000, 10 * this.daysOverdue(today)); } } // to represent a CD in a library class CD extends LibItem { String artist; CD(int catNo, String title, int dueDate, String artist) { super(catNo, title, dueDate); this.artist = artist; } int fine(int today){ return Math.min(2000, 50 * this.daysOverdue(today)); } } // to represent a magazine in a library class Magazine extends LibItem { int vol; int no; Magazine(int catNo, String title, int dueDate, int vol, int no) { super(catNo, title, dueDate); this.vol = vol; this.no = no; } int fine(int today){ return 30 * this.daysOverdue(today); } } class Examples { Examples() {} Book bk = new Book(123, "Beaches", 45, "PC"); CD cd = new CD(234, "Pearl", 40, "JJ"); Magazine mw = new Magazine(143, "MW", 20, 14, 3); // tests for the method smallerThan boolean testSmallerThan1 = bk.smallerThan(cd) == true; boolean testSmallerThan2 = mw.smallerThan(bk) == false; // tests for the method daysOverdue boolean testdaysOverdue1 = bk.daysOverdue(48) == 3; boolean testdaysOverdue2 = bk.daysOverdue(40) == 0; boolean testdaysOverdue3 = cd.daysOverdue(48) == 8; boolean testdaysOverdue4 = cd.daysOverdue(30) == 0; boolean testdaysOverdue5 = mw.daysOverdue(48) == 28; boolean testdaysOverdue6 = mw.daysOverdue(18) == 0; // tests for the method fine boolean testdaysFine1 = bk.fine(48) == 30; boolean testdaysFine2 = bk.fine(40) == 0; boolean testdaysFine3 = bk.fine(400) == 2000; boolean testdaysFine4 = cd.fine(48) == 400; boolean testdaysFine5 = cd.fine(30) == 0; boolean testdaysFine6 = cd.fine(300) == 2000; boolean testdaysFine7 = mw.fine(28) == 240; boolean testdaysFine8 = mw.fine(18) == 0; boolean testdaysFine9 = mw.fine(220) == 6000; }