In the previous exercise, we looked at extreme testing, and how encapsulation makes that possible. In this one, we’ll see some of the tradeoffs that can be made with fields and methods.
This object is very similar to the ones in the last couple of exercises. A
single instance variable this time, one mutator method (setRadius()
)
and three accessor methods. (The surface area of a sphere is
, and the volume of a sphere is
.)
This is clearly a tester and not just a simple driver program; I have tests that are passing or failing. Writing this was pretty annoying, but I wanted to show you the idea without making it too crazy, so I had to use my calculator with a couple of test cases to see what they ought to be. In a future exercise we’ll see a much better way to do a lot of tests like this without repeating so much code, but it’s too complicated for now.
Line 4 instantiates a SphereCalc object, then line 6 sets its radius to 5
.
Starting down on line 29, there’s a little helper function I wrote. It receives
two double
s and returns true
if the absolute value of their difference is
very small (smaller than ). It’s best to avoid using
just ==
on two floating-point values since sometimes repeating decimals or
slight differences in rounding will make two values that ought to be the same
slightly different.
(Instead of isNear()
I probably could have called the function
isVeryCloseToEqual()
but I didn’t feel like typing that more than once.)
So lines 7 through 14 just call the methods from SphereCalc
and make sure
they return numbers close enough to the expected values. If so, we print out
“PASS” and if not we print out “FAIL” and a little bit of detail. Normally
you’d want to print out more information with the failure (like which radius
failed and what the expected value was and what you got instead), but I didn’t
want to clutter up the code.
Oh, and in case you’ve never seen it before, an E
inside a floating-point
number means “times ten to the”. On line 21, 4.18879E-3
means
) A.K.A. 0.00418879
.
Okay, so now let’s look at an slightly different way of splitting up the
work in the SphereCalc
object. (You’ll need to type this one in, too,
if you’re going to do the Study Drill.)
SphereCalc2
has three fields instead of just one. And inside the
setRadius()
mutator method, it doesn’t just set the radius, it also goes
ahead and computes the surface area and volume, too.
There’s a trade-off here. Each instance of a SphereCalc2
object would take up
slightly more memory than each SphereCalc
object, because of the extra
fields, and creating a instance of a SphereCalc2
object would take slightly
longer than instantiating a SphereCalc
object because it does more
calculations up front.
However, if you had a SphereCalc
object and you called getVolume()
over and over again in a loop or something, it would have to do that
calculation over and over. Whereas a SphereCalc2
object has already done
the calculation and just gets to return that single value over and over.
Which approach is better? You’d have to run tests and see how your object is being used to find out.
SphereCalc2
has one serious problem, however. Well, it’s more like a
vulnerability than a problem. When someone is using a SphereCalc2
object
and they want to change the radius, we expect them to use the provided
setRadius()
method. We hope that’s what they will do.
But as you might recall from TVActorDriver.java
way back in Exercise 4,
a driver class can access instance variables directly. At least, the way
we’ve been writing them up to this point.
What’s to prevent someone from writing code like this?
Now, it probably wouldn’t look so evil. It might be like on line 16 on the tester. Instead of writing:
It’s more efficient, right? Who wants to call a method when you can just put a value in a variable?!? Not this guy!
Anyway, hopefully that illustrates the “problem”. For the solution, you’ll have to come back in the next exercise.
“Learn Object-Oriented Java the Hard Way” is ©2015–2016 Graham Mitchell