/* --- CSU213 Spring 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 5: Methodical Methods Goals: - learn to define and use methods in simple classes - design recipe for methods Introduction: So far we have considered only data. We have not computed anything, did not ask questions about the objects we defined, in short, we have not written any programs. Part of the reason is that the functions in languages like Java need to know what kind of data are they consume and what kind of data they produce. In Scheme, this was only specified in a comment, in other languages, like Java, the programmer must explicitly write down what is the type of each function argument. The functions in Java are always associated with some class of data --- and are called either memeber functions, or more often 'methods'. Methods in a class are designed to answer questions about instances of this class, or produce new objects in the class from existing objects. So we may have a method in the class Book which determines whether this book has a given title, or the class that represents geometric chapes may have a method which produces a new shape of the same size and color as the original one, but moved to a new location. However, the design of methods follows the same design recipe we have learned in HtDP. Example: Suppose we want to look at the data in a student's transcript. Each recoord in the transcript reflect the information about one course student has completed. The information provided there includes the course number and title, the grade earned, and the number of credits the course carries. (Certainly, this may not be all that is in the record, but for our purposes this is sufficient.) We can represent the course number and the credits as an integer, the course name as a String, and the grade as a 'double'. +--------------+ | GradeRecord | +--------------+ | int number | | String title | | int credits | | double grade | +--------------+ // to represent a grade record on a transcript class GradeRecord { int number; String title; int credits; double grade; GradeRecord(int number, String title, int credits, double grade) { this.number = number; this.title = title; this.credits = credits; this.grade = grade; } // add the methods here ... } Of course, in scheme, the data definition and the structure definition would be: ;; A GradeRecord is (make-gr Number String Number Number) (define-struct gr (number title credits grade)) Before we design any methods, we make a couple of examples of grade records: GradeRecord fund = new GradeRecord(211, "Fundamentals", 4, 3.66); GradeRecord ovw = new GradeRecord(220, "Overview", 1, 4.0); GradeRecord algo = new GradeRecord(690, "Algorithms", 4, 3.0); And, again, in Scheme these would be: (define fund (make-gr 211 "Fundamentals" 4 3.66) (define ovw (make-gr 220 "Overview" 1 4.0) (define algo (make-gr 690 "Algorithms" 4 3.0) The first method we design computes the quality points for this grade record. (To compute the grade point average (or at NU it used to be called QPA for quality point average) each grade is multiplied by the number of credits the course carries (this gives you the quality points for the grade record) and then the sum of quality points is divided by the total number of credits on the transcript. 1. Problem analysis and data definitions: The only piece of information the method needs is the instance of the class GradeRecord for which we are computing the quality points. It will produce a double value that represents the quality points. 2. Purpose and contract (we will call it 'header' or 'signature'): In Scheme this would be: ;; compute the quality points for a grade record ;; q-point: GradeRecord -> Number (define (qPoint a-gr) ... ) In Java this becomes: // compute the quality points for this grade record double qPoint(){...} The method header includes not only the names for the method arguments, but for each of them it also specifies what type of data it represents. However, we have no arguments here. The only piece of data this method can use is the instance of the GradeRecord class that invoked (called) the method. the only way Java knows which method to call is by looking at the type of data that invoked the method and finding the method definition within the class definition of that class. (Later we will see that there is more going on here.) The double at the beginning specifies what type of data the method produces. The parentheses after the name of the method hold the list of parameters for this method - but there are none specified. That is, because the grade recordfor which we are computing the price is already known -- as 'this', the object that invoked the method -- as we will see in the examples. 3. Examples: we expect the quality point averages of our three examples to be 14.64, 4.0, and 12.0 repsectively. That means, fund.qPoints should be --> 14.64 ovw.qPoints should be --> 4.0 algo.qPoints should be --> 12.0 We see, that in each case the instance of the book for which we are computing the grade record instance appears before the method, followed by a '.' 'this' grade record in our purpose statement refer to this implicit parameter. 4. Template: The template lists all parts of data available for the computation inside of the body of the method. Even though we have no parameters, the Grade Record object which invoked the method contains several parts, and so the template will be: ... this.number ... ... this.title ... ... this.credits ... ... this.grade ... where 'this' refers to the instance which invoked the method. Only 'this.credits' and 'this.grade' is relevant for our method, and the body becomes: return this.grade * this.credits; Notice that Java uses the infix notation for the arithmetic (and other) operations. The complete method is shown below: // compute the quality points for this grade record double qPoints(){ return this.grade * this.credits; } We copy the examples of data and of the method invocation into the ExamplesBooks class. It is not easy to compare two double-s for equality. We know this from HtDP. So, we add a method almostSame to the class ExamplesGraderecord that determines whether two doubles are close enough. We actually compare the square of the differences between them. (We know this is always a positive number - and just check whether it is smaller than a given small value of epsilon (eps). class ExamplesGradeRecord{ ExamplesGradeRecord() {} GradeRecord fund = new GradeRecord(211, "Fundamentals", 4, 3.66); GradeRecord ovw = new GradeRecord(220, "Overview", 1, 4.0); GradeRecord algo = new GradeRecord(690, "Algorithms", 4, 3.0); // determine whether two double values are almost the same boolean almostSame(double d1, double d2, double eps) { return ((d1 - d2) * (d1 -d2) < eps); } boolean test1 = this.almostSame(this.fund.qPoints(), 14.64, 0.01); boolean test2 = this.almostSame(this.ovw.qPoints(), 4.0, 0.01); boolean test3 = this.almostSame(this.algo.qPoints(), 12.0, 0.01); } Next, we want to figure out whether a student earned at least some given number of quality points in this course. 1. Problem analysis and data analysis: The method consumes the threshold value and produces a boolean value. 2. Purpose and header: // does this grade record earn quality points above the given level? boolean okCourse(int level) {...} 3. Examples: fun.okCourse(14) == false algo.okCourse(14) == true 4. Template: --- we can now add the method we already defined --- ... this.number ... ... this.title ... ... this.credits ... ... this.grade ... ... this.qPoints() ... 5. Body: return this.qPoints() >= level; so that the whole method becomes: // does this cours record earn quality points above the given level? boolean okCourse(int level) { return this.qPoints() >= level; } 6. Tests: -- to be inserted in the ExamplesGradeRecord class: boolean test4 = this.fund.okCourse(14) == true; boolean test5 = this.algo.okCourse(14) == false; */ /* Finally, we want to figure out whether one grade record has a higher grade than another one. 1. Problem analysis and data analysis: The method consumes one additional grade record and produces a boolean value. 2. Purpose and header: // is the grade in this grade record higher that in the given record boolean higherGrade(GradeRecord that) {...} 3. Examples: fund.higherThan(ovw) == false fund.higherThan(algo) == true 4. Template: --- we can now add the methods we already defined --- ... this.number ... ... this.title ... ... this.credits ... ... this.grade ... ... this.qPoints() ... ... this.okCourse(int level) ... --- and also all these =fields and methods for 'that' grade record --- ... that.number ... ... that.title ... ... that.credits ... ... that.grade ... ... that.qPoints() ... ... that.okCourse(int level) ... The first part of the template should be retained in the class definition as a comment right after teh constructor. It grows as more methods are added to the class. It can be just copied and pasted into the method body as it is being developed, and erased once not needed. The second part is specific to this method, and so should be written there. 5. Body: return this.grade > that.grade; so that the whole method becomes: // is the grade in this grade record higher that in the given record boolean higherGrade(GradeRecord that) { return this.grade > that.grade; } 6. Tests: -- to be inserted in the ExamplesGradeRecord class: boolean test6 = this.fund.higherGrade(this.ovw) == true; boolean test7 = this.fund.higherGrade(this.algo) == false; */ /*Here is our code all together: +--------------+ | GradeRecord | +--------------+ | int number | | String title | | int credits | | double grade | +--------------+ */ // to represent a grade record on a transcript class GradeRecord { int number; String title; int credits; double grade; GradeRecord(int number, String title, int credits, double grade) { this.number = number; this.title = title; this.credits = credits; this.grade = grade; } /* TEMPLATE: ... this.number ... ... this.title ... ... this.credits ... ... this.grade ... ... this.qPoints() ... ... this.okCourse(int level) ... ... this.higherGrade(GradeRecord that) ... */ // compute the sale price of this book double qPoints(){ return this.grade * this.credits; } // does this grade record earn quality points above the given level? boolean okCourse(int level) { return this.qPoints() >= level; } // is the grade in this grade record higher that in the given record boolean higherGrade(GradeRecord that) { return this.grade > that.grade; } } class ExamplesGradeRecord{ ExamplesGradeRecord() {} GradeRecord fund = new GradeRecord(211, "Fundamentals", 4, 3.66); GradeRecord ovw = new GradeRecord(220, "Overview", 1, 4.0); GradeRecord algo = new GradeRecord(690, "Algorithms", 4, 3.0); // determine whether two double values are almost the same boolean almostSame(double d1, double d2, double eps) { return ((d1 - d2) * (d1 -d2) < eps); } // test the method qPoints: boolean test1 = this.almostSame(this.fund.qPoints(), 14.64, 0.01); boolean test2 = this.almostSame(this.ovw.qPoints(), 4.0, 0.01); boolean test3 = this.almostSame(this.algo.qPoints(), 12.0, 0.01); // test the method okCourse: boolean test4 = this.fund.okCourse(14) == true; boolean test5 = this.algo.okCourse(14) == false; // test the method higherThan: boolean test6 = this.fund.higherGrade(this.ovw) == false; boolean test7 = this.fund.higherGrade(this.algo) == true; }