Pedagogical advantage of this code
This code taken from https://cseducators.stackexchange.com/a/3936/204 by @peter. Where he states: “A student can absolutely take a C-style approach to this, making little to no use of the object-oriented paradigm of Java.” Then showing this as the alternative.
public class Temperature
{
// data members/instance variables
private String scale;
private double temp;
// constructor
public Temperature(String scale, double temp)
{
this.scale = scale;
this.temp = temp;
}
// getters
public String getScale()
{
return scale;
}
public double getTemp()
{
return temp;
}
// setters
public void setScale(String newScale)
{
scale = newScale;
}
public void setTemp(double newTemp)
{
temp = newTemp;
}
// conversion methods
public double cToF()
{
return temp * 9 / 5 + 32;
}
public double fToC()
{
return (temp - 32) * 5 / 9;
}
}
public class TemperatureRunner
{
public static void main(String args[])
{
Temperature hotDay = new Temperature("F", 101);
System.out.println(hotDay.getScale());
System.out.println(hotDay.getTemp() + " F");
System.out.printf("%.2f C\n", hotDay.fToC());
System.out.println();
Temperature coldDay = new Temperature("C", -10);
System.out.println(coldDay.getScale());
System.out.println(coldDay.getTemp() + " C");
System.out.printf("%.2f F\n", coldDay.cToF());
}
Software Engineer objections to this code:
- I thinly discussed structure: getters/setters on everything.
- Verb names for query functions (Though this is common in Java: this was introduced by Java-Beans, to ease machine parsing)
- Mutable: not thread safe. Even with locks, would not be thread save, as there are two setters, that can be interleaved. Mutability also leads to other bugs, that will exhibit long before student gets to concurrency.
- Implicit preconditions on cToF, and fToC (e.g. can call CToF on an F ).
- Scale is not used. Client is handling formatting, and Scale.
I don't see what is gained by this code, and it is much larger than the C
code.
I had a go at re-writing it, to be more OO.
public class Temperature
{
//data
private String scale;
private double temp;
// constructors
public Temperature(String scale, double temp)
{
this.scale = scale;
this.temp = temp;
}
//functions
public double cToF(double c)
{
return c * 9 / 5 + 32;
}
public double fToC(double f)
{
return (f - 32) * 5 / 9 ;
}
//converters
public Temperature asF()
{
if (this.scale == "F")
{
return this;
}
else
{
return new Temperature("F", cToF(temp));
}
}
public Temperature asC()
{
if (this.scale == "C")
{
return this;
}
else
{
return new Temperature("C", fToC(temp));
}
}
//formatters
public String formatted()
{
return String.valueOf(temp)+scale;
}
}
public class TemperatureRunner
{
public void run()
{
Temperature hotDay = new Temperature("F", 101);
Temperature coldDay = new Temperature("C", -10);
System.out.printf("%S\n", hotDay.formatted());
System.out.printf("%S\n", hotDay.asC().formatted());
System.out.printf("%S\n", hotDay.asF().formatted());
System.out.println();
System.out.printf("%S\n", coldDay.formatted());
System.out.printf("%S\n", coldDay.asC().formatted());
System.out.printf("%S\n", coldDay.asF().formatted());
}
}
public class Main
{
public static void main(String args[])
{
new TemperatureRunner().run();
}
}
This can also be used as a basis for teaching other features. I evolved it into this (with the addition of an enum
, and some final
& static
:
public class Temperature
{
public enum Scale {
F ("°F"),
C ("°C");
private final String units;
//Constructor
private Scale(String units){ this.units = units; }
//getter
public String toString() {return this.units;}
};
//data
private final Scale scale;
private final double temp;
// constructors
public Temperature(Scale scale, double temp)
{
this.scale = scale;
this.temp = temp;
}
//functions
public static double cToF(double c)
{
return c * 9 / 5 + 32;
}
public static double fToC(double f)
{
return (f - 32) * 5 / 9 ;
}
//converters
public Temperature asF()
{
if (this.scale == Scale.F)
{
return this;
}
else
{
return new Temperature(Scale.F, cToF(temp));
}
}
public Temperature asC()
{
if (this.scale == Scale.C)
{
return this;
}
else
{
return new Temperature(Scale.C, fToC(temp));
}
}
//formatters
public String formatted()
{
return String.valueOf(temp)+scale;
}
}
public class TemperatureRunner
{
public void run()
{
Temperature hotDay = new Temperature(Temperature.Scale.F, 101);
Temperature coldDay = new Temperature(Temperature.Scale.C, -10);
System.out.printf("%S\n", hotDay.formatted());
System.out.printf("%S\n", hotDay.asC().formatted());
System.out.printf("%S\n", hotDay.asF().formatted());
System.out.println();
System.out.printf("%S\n", coldDay.formatted());
System.out.printf("%S\n", coldDay.asC().formatted());
System.out.printf("%S\n", coldDay.asF().formatted());
}
}
You could also add methods to add and subtract temperatures. You may ask why not expose the insides and let the user of the class do it. The purist answer is that it breaks encapsulation. The pragmatic answer is that then some one will try multiplication or division (and get it wrong). I often here people say that 30° is double 15°, it is not.
[I am not a Java programmer, so sorry if this is not the best. However all code is compiled and tested.]
So is there any pedagogic value in the first? Does this value out way the harm of having to un-learn the bad practice? What am I missing?
Added by Buffy:
Here is a much simpler Temperature class. I think it is probably suitable for an early example.
public class Temperature{
public double getF(){return temperatureF;}
public double getC(){return (temperatureF - 32.0) * 5.0 / 9.0;}
public void setF(double tempF){
temperatureF = tempF;
}
public void setC(double tempC){
temperatureF = tempC*9.0/5.0 + 32.0;
}
private double temperatureF = 32.0;
/* Could be improved with a check for a legal temperature (i.e. >= absolute zero) */
}