statement() works by looping through all rentals; for each rental, it retrieves its movie and the number of days it was rented; it also retrieves the price code of the movie
It then calculates the price for each movie rental and the number of frequent renter points and returns the generated statement as a string
public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private int _priceCode; public Movie(String title, int priceCode) { _title = title; _priceCode = priceCode; } public int getPriceCode() { return _priceCode; } public void setPriceCode(int arg) { _priceCode = arg; } public String getTitle() { return _title; } }
public class Rental { private Movie _movie; private int _daysRented; public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; } }
import java.util.Enumeration; import java.util.Vector; public class Customer { private String _name; private Vector _rentals = new Vector(); public Customer (String name) { _name = name; } public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName() { return _name; } public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); // determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; } // add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { frequentRenterPoints++; } // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } }
import java.io.*; public class useCustomer { private static Movie monty = new Movie("Life of Brian", Movie.REGULAR); private static Movie terry = new Movie("Brazil", Movie.REGULAR); private static Movie disney = new Movie("Snow White", Movie.CHILDRENS); private static Movie nicole = new Movie("Moulin Rouge", Movie.NEW_RELEASE); private static Rental r1 = new Rental(monty, 7); private static Rental r2 = new Rental(terry, 14); private static Rental r3 = new Rental(disney, 4); private static Rental r4 = new Rental(nicole, 2); private static Customer ken = new Customer("Ken Anderson"); public static void main(String[] args) { try { ken.addRental(r1); ken.addRental(r2); ken.addRental(r3); ken.addRental(r4); File target = new File(args[0]); LineNumberReader in = new LineNumberReader(new FileReader(target)); String targetStatement = null; String line = null; while ((line = in.readLine()) != null) { if (targetStatement != null) { targetStatement += line; } else { targetStatement = line; } targetStatement += "\n"; } // strip off last \n targetStatement = targetStatement.substring(0, targetStatement.length()-1); System.out.println("Current Statement:\n\n" + ken.statement()); System.out.println(""); System.out.println(""); System.out.println("Target Statement:\n\n" + targetStatement); System.out.println(""); if (targetStatement.equals(ken.statement())) { System.out.println("Test Passed."); } else { System.out.println("Test Failed."); } } catch (Exception e) { e.printStackTrace(); } } }
Rental Record for Ken Anderson Life of Brian 9.5 Brazil 20.0 Snow White 3.0 Moulin Rouge 6.0 Amount owed is 38.5 You earned 5 frequent renter points
Note: Lines 2-5 have a single tab character both before and after the movie name
$ java -classpath . useCustomer targetStatement.txt Current Statement: Rental Record for Ken Anderson Life of Brian 9.5 Brazil 20.0 Snow White 3.0 Moulin Rouge 6.0 Amount owed is 38.5 You earned 5 frequent renter points Target Statement: Rental Record for Ken Anderson Life of Brian 9.5 Brazil 20.0 Snow White 3.0 Moulin Rouge 6.0 Amount owed is 38.5 You earned 5 frequent renter points Test Passed.
// determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; }
private double amountFor(Rental each) { double thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; } return thisAmount; }
thisAmount = amountFor(each);
public class Customer { … public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); // determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; } // add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { frequentRenterPoints++; } // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } … }
public class Customer { … public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental) rentals.nextElement(); // determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; } // add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { frequentRenterPoints++; } // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n"; totalAmount += thisAmount; } // add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; } private double amountFor(Rental each) { double thisAmount = 0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) { thisAmount += (each.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (each.getDaysRented() > 3) { thisAmount += (each.getDaysRented() - 3) * 1.5; } break; } return thisAmount; } …
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line
thisAmount = amountFor(each);
// add frequent renter points
frequentRenterPoints++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
(each.getDaysRented() > 1)) {
frequentRenterPoints++;
}
// show figures for this rental
result += "\t" + each.getMovie().getTitle() +
"\t" + String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points";
return result;
}
$ java -classpath . useCustomer targetStatement.txt Current Statement: Rental Record for Ken Anderson Life of Brian 9.0 Brazil 20.0 Snow White 2.0 Moulin Rouge 6.0 Amount owed is 37.0 You earned 5 frequent renter points Target Statement: Rental Record for Ken Anderson Life of Brian 9.5 Brazil 20.0 Snow White 3.0 Moulin Rouge 6.0 Amount owed is 38.5 You earned 5 frequent renter points Test Failed.
Any fool can write code that a computer can understand. Good programmers write code that humans can understand
private double amountFor(Rental aRental) { double result = 0; switch (aRental.getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (aRental.getDaysRented() > 2) { result += (aRental.getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += aRental.getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (aRental.getDaysRented() > 3) { result += (aRental.getDaysRented() - 3) * 1.5; } break; } return result; }
amountFor(each)
to each.getCharge()
public class Rental { … public double getCharge() { double result = 0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result += 2; if (getDaysRented() > 2) { result += (getDaysRented() - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += getDaysRented() * 3; break; case Movie.CHILDRENS: result += 1.5; if (getDaysRented() > 3) { result += (getDaysRented() - 3) * 1.5; } break; } return result; } … }
No major changes; however Customer is now a smaller class and Rental has stopped being “just a data holder” and can now actually do something!
Definitely making progress!
public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { frequentRenterPoints++; } // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } // add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }
// add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { frequentRenterPoints++; }
public int getFrequentRenterPoints(Rental each) { if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (each.getDaysRented() > 1)) { return 2; } else { return 1; } }
frequentRenterPoints += getFrequentRenterPoints(each);
public int getFrequentRenterPoints() { if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && (getDaysRented() > 1)) { return 2; } else { return 1; } }
public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n"; totalAmount += each.getCharge(); } // add footer lines result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points"; return result; }
Customer continues to get smaller (but with better structure)
Rental continues to get larger (but more useful)
private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } return result; }
public String statement() {
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
frequentRenterPoints += each.getFrequentRenterPoints();
// show figures for this rental
result += "\t" + each.getMovie().getTitle() +
"\t" + String.valueOf(each.getCharge()) + "\n";
}
// add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points";
return result;
}
private int getTotalFrequentRenterPoints() { int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; }
public String statement() {
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// show figures for this rental
result += "\t" + each.getMovie().getTitle() +
"\t" + String.valueOf(each.getCharge()) + "\n";
}
// add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) +
" frequent renter points";
return result;
}
Customer's structure continues to evolve; it now has two methods that can be reused across statement() and htmlstatement()
Note: statement()'s algorithm has now evolved. It used to perform the loop only once, now it performs it three times! Is this a performance problem? Maybe, we can't say without checking the program with a profiler
public String htmlStatement() { Enumeration rentals = _rentals.elements(); String result = "<html><head><title>Statement</title></head><body>\n"; result += "<p><h1>Rental Record for <em>" + getName() + "</em></h1></p>\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "<br />\n"; } // add footer lines result += "<p>Amount owed is <em>" + String.valueOf(getTotalCharge()) + "</em></p>\n"; result += "<p>You earned <em>" + String.valueOf(getTotalFrequentRenterPoints()) + "</em> frequent renter points</P>\n"; result += "</body></html>"; return result; }
Steps of Solution: Create new class hierarchy containing a single version of the duplicated method. Extract the portions that vary into new smaller methods. These methods are declared abstract on the superclass. The original method invokes these abstract methods at the appropriate place. These abstract methods are implemented in the subclasses.
public class Movie { … double getCharge(int daysRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += daysRented * 3; break; case Movie.CHILDRENS: result += 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } break; } return result; } public int getFrequentRenterPoints(int daysRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && (daysRented > 1)) { return 2; } else { return 1; } } …
public double getCharge() { return getMovie().getCharge(getDaysRented()); } public int getFrequentRenterPoints() { return getMovie().getFrequentRenterPoints(getDaysRented()); }
Movie has new methods, finally making the transition from data holder to object; these methods will allow us to handle new types of movie easily
But movies can change type! A children’s movie when it is first released is a “new release”; later it becomes a children’s movie. So this approach won’t work!
A movie has a particular state: its charge (and its renter points) depend on that state; so we can use the state pattern to handle new types of movies (for now, at least)
Note: In this formulation, the differences between the State pattern and the Strategy pattern we saw in a previous lecture are negligible
public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private Price _price; public Movie(String title, int priceCode) { _title = title; setPriceCode(priceCode); } public int getPriceCode() { return _price.getPriceCode(); } public void setPriceCode(int arg) { switch (arg) { case REGULAR: _price = new RegularPrice(); break; case CHILDRENS: _price = new ChildrensPrice(); break; case NEW_RELEASE: _price = new NewReleasePrice(); break; default: throw new IllegalArgumentException("Incorrect Price Code"); } } … }
public abstract class Price { abstract int getPriceCode(); } public class RegularPrice extends Price { int getPriceCode() { return Movie.REGULAR; } } public class NewReleasePrice extends Price { int getPriceCode() { return Movie.NEW_RELEASE; } } public class ChildrensPrice extends Price { int getPriceCode() { return Movie.CHILDRENS; } }
public abstract class Price { abstract int getPriceCode(); public double getCharge(int daysRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } break; case Movie.NEW_RELEASE: result += daysRented * 3; break; case Movie.CHILDRENS: result += 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } break; } return result; } }
double getCharge(int daysRented) { return _price.getCharge(daysRented); }
public abstract class Price { public abstract int getPriceCode(); public abstract double getCharge(int daysRented); } public class NewReleasePrice extends Price { int getPriceCode() { return Movie.NEW_RELEASE; } double getCharge(int daysRented) { return daysRented * 3; } } public class RegularPrice extends Price { int getPriceCode() { return Movie.REGULAR; } double getCharge(int daysRented) { double result = 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } return result; } } public class ChildrensPrice extends Price { int getPriceCode() { return Movie.CHILDRENS; } double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } return result; } }
public int getFrequentRenterPoints(int daysRented) { return _price.getFrequentRenterPoints(daysRented); }
public int getFrequentRenterPoints(int daysRented) { return 1; }
public int getFrequentRenterPoints(int daysRented) { return (daysRented > 1) ? 2 : 1; }