/* --- CSU213 Spring 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 12: Abstracting with classes Goals: - Learning to design abstract classes by lifting fields and methods Introduction: The design recipe for abstractions tells us to look for similarities between several parts of the code, identify the differences, replace them with parameters, and test the result by making sure the original tests work as before. Our goal is to learn how we can use an abstract class to represent the common fields and common methods of its variants, and how the variants can use this abstraction. Our examples involve weather recordings --- of air pressure and temperature. The air pressure recordings will be in hPa (hectoPascal-s), the temperature in degrees Fahrenheit. We start by observing the following two classes: // recording air pressure measurements [in hPa] class Pressure { int high; int today; int low; Pressure(int high,int today,int low) { this. high = high; this. today = today; this. low = low; } int dHigh() { return this. high - this. today; } int dLow() { return this. today - this. low; } String asString() { return String.valueOf(high) .concat("-") .concat(String.valueOf(low)) .concat("hPa"); } } // recording temperature measurements [in F] class Temperature { int high; int today; int low; Temperature(int high,int today,int low) { this. high = high; this. today = today; this. low = low; } int dHigh() { return this. high - this. today; } int dLow() { return this. today - this. low; } String asString() { return String.valueOf(high) .concat("-") .concat(String.valueOf(low)) .concat("F"); } } We observe that the two methods are the same in both classes. The fields are the same as well. The only difference is in the method 'asString'. Even there, most of the method body is the same. We design a super class 'Recordings' and lift to that class the declarations of the fields, and the two identical method definitions. We get the following class hierarchy: // fig:recordings-super2 // Recording weather information with a superclass (version~1) // to represent weather-like recordings class Recording { int high; int today; int low; Recording(int high,int today,int low) { this. high = high; this. today = today; this. low = low; } int dHigh() { return this. high - this. today; } int dLow() { return this. today - this. low; } } // to represent recordings of air pressure class Pressure extends Recording { Pressure(int high,int today,int low) { super(high,today,low); } String asString() { return String.valueOf(high) .concat("-") .concat(String.valueOf(low)) .concat("hPa"); } } // to represent recordings of temperature class Temperature extends Recording { Temperature(int high,int today,int low) { super(high,today,low); } String asString() { return String.valueOf(high) .concat("-") .concat(String.valueOf(low)) .concat("F"); } } We run the tests from the previous version and make sure they work. We now look at the method 'asString'. We can lift most of the body to the super class and override the method by adding the String that represents the unit to the result produced by the 'super' method: in the class Recording we get: String asString() { return String.valueOf(high) .concat("-") .concat(String.valueOf(low)); } In the class Pressure we override this method as follows: String asString() { return super. asString() .concat("hPa"); } The method first invokes the method in the super class, then adds to the resulting String the String "hPa" that represents our unit of measurement. In the Temperature class we proceed the same way: String asString() { return super. asString() .concat("F"); } However, done this way, we have no guarantee that the subclass will specify the units of measurement. One way we can make sure that the unit is always given is by adding a new field of the type String that represents the unit of measurement to the super class (and therefore to all subclasses): // Recording weather information with a superclass (version~2) // fig:recordings-super3 class Recording { int high; int today; int low; String unit; Recording(int high,int today,int low,String unit) { this. high = high; this. today = today; this. low = low; this. unit = unit; } int dHigh() { return this. high - this. today; } int dLow() { return this. today - this. low; } String asString() { return String.valueOf(high).concat("-").concat(String.valueOf(low)). concat(this.unit); } } class Pressure extends Recording { Pressure(int high,int today,int low) { super(high,today,low,"hPa"); } } class Temperature extends Recording { Temperature(int high,int today,int low) { super(high,today,low,"F"); } } This is all well, but there is nothing that prevents us from using the Recording class directly - confusing different units of measurement in the process. To make sure that each different kind of measurement is represented by a different class, we make the Recording class abstract by requiring that each subclass supplies a method 'unit()' that produces the desired String to represent the unit of measurement. We get the following: // Recording weather information with a superclass (version~3) // fig:recordings-super4 abstract class ARecording { int high; int today; int low; ARecording(int high,int today,int low) { this.high = high; this.today = today; this.low = low; } int dHigh() { return this. high - this. today; } int dLow() { return this. today - this. low; } String asString() { return String.valueOf(high).concat("-").concat(String.valueOf(low)).concat(this.unit()); } abstract String unit(); } class Temperature extends ARecording { Temperature(int high,int today,int low) { super(high,today,low); } String unit() { return "F"; } } class Pressure extends ARecording { Pressure(int high,int today,int low) { super(high,today,low); } String unit() { return "hPa"; } } The method 'unit()' is called a 'hook' for the 'template' method 'asString'. Together the two method form the "Template and Hook" design pattern --- requiring that the user fills in the needed information for the nearly complete implementation fo the solution. We can further continut this process by observing that the temperature can be recorded in several different measurement scales (Fahrenheit vs. Celsius) - and delegate this choice to yet another collection of subclasses of the Temperature class --- employing again the "Template and Hook" design pattern. */