|
|
|
Sponsored Link •
|
|
Advertisement
|
Minimally coupled utility methods
The least coupled method that is possible in Java is a utility method
that:
A good utility method
For example, the method convertOzToMl() shown below
accepts an int as its only input and returns an
int as its only output:
// In source packet in file coupling/ex1/Liquid.java
class Liquid {
private static final double FL_OUNCES_PER_ML = 12.0/355.0;
private static final double ML_PER_FL_OUNCE = 355.0/12.0;
/**
* Converts fluid ounces to milliliters
*/
public static int convertOzToMl(int ounces) {
double d = ounces * ML_PER_FL_OUNCE;
d += 0.5; // Must add .5 because (int) truncates
return (int) d; // Result now rounded up if fraction >= .5
}
}
Note that even though the above method makes use of a constant value, the constant value doesn't increase the method's coupling. (This is not only true conceptually, but also in how Java programs are compiled. As mentioned previously in this article, if a class uses a constant, even if it is from another class, its class file gets its own local copy of that constant value.)
To use this method, another method simply passes in the number of ounces and stores the returned number of milliliters:
// In source packet in file coupling/ex1/Liquid.java
class Example1 {
public static void main(String[] args) {
int mlFor8Oz = Liquid.convertOzToMl(8);
int mlFor12Oz = Liquid.convertOzToMl(12);
int mlFor16Oz = Liquid.convertOzToMl(16);
System.out.println("Ml for 8 oz is: " + mlFor8Oz);
System.out.println("Ml for 12 oz is: " + mlFor12Oz);
System.out.println("Ml for 16 oz is: " + mlFor16Oz);
}
}
Your aim when you write a utility method, such as
convertOzToML(), should be to take input only from
parameters and express output only through parameters or a return value
or exception. In Figure 2, you can see a graphical depiction of this
kind of method.
Figure 2. A good (minimally-coupled) utility method |
Figure 2 shows a structure chart, from structured (not object-oriented) design. Although structure charts are not generally useful in an object-oriented design process, they are useful for graphically depicting the input and output to methods. For this reason, I'll be using structure charts in this article to help you visualize the coupling of methods.
A bad utility method
One way to increase the coupling of a utility method (or any other kind
of method) is to pass objects that contain input data or are the
recipient of output data, when the objects are not vital to the
performance of the method. For example, perhaps when you first write
convertOzToMl(), you plan always to put its output into a
CoffeeCup object, as in:
// In source packet in file coupling/ex2/Example2.java
class Example2 {
public static void main(String[] args) {
CoffeeCup cup = new CoffeeCup();
int amount = Liquid.convertOzToMl(16);
cup.add(amount);
//...
}
}
If so, you might be tempted to write the convertOzToMl()
method like this:
// In source packet in file coupling/ex3/Liquid.java
class Liquid {
private static final double FL_OUNCES_PER_ML = 12.0/355.0;
private static final double ML_PER_FL_OUNCE = 355.0/12.0;
/**
* Converts fluid ounces to milliliters
*/
public static void convertOzToMl(int ounces, CoffeeCup cup) {
double d = ounces * ML_PER_FL_OUNCE;
d += 0.5;
cup.add((int) d);
}
}
So you could use it like this:
// In source packet in file coupling/ex3/Example3.java
class Example3 {
public static void main(String[] args) {
CoffeeCup cup = new CoffeeCup();
Liquid.convertOzToMl(16, cup);
//...
}
}
The problem here is that convertOzToMl() is now coupled to
the CoffeeCup class. This is less flexible than the first
version of convertOzToMl(), which returned the milliliters
as an int. If later, someone wanted to convert ounces to
milliliters for some purpose that didn't involve a coffee cup, they
would have to rewrite the method, write a different method, or
create a CoffeeCup object just to hold the output.
In Figure 3 you can see a graphical depiction of this kind of method.
Figure 3. A bad utility method |
A truly ugly utility method
The worst way to write the convertOzToMl() method (the way
that yields the maximum coupling) is to take an input from a
public static variable and put the output in
another public static variable. A
public static (but not final)
variable in Java is equivalent in functionality and danger to global
variables of C or C++. Here's an example:
// In source packet in file coupling/ex4/Liquid.java
// THIS APPROACH WORKS, BUT MAKES THE CODE HARD TO UNDERSTAND
// AND HARD TO CHANGE
class Liquid {
private static final double FL_OUNCES_PER_ML = 12.0/355.0;
private static final double ML_PER_FL_OUNCE = 355.0/12.0;
/**
* Converts fluid ounces to milliliters
*/
public static void convertOzToMl() {
double d = PurpleZebra.k * ML_PER_FL_OUNCE;
d += 0.5;
FlyingSaucer.q = (int) d;
}
}
// In source packet in file coupling/ex4/FlyingSaucer.java
class FlyingSaucer {
public static int q;
//...
}
// In source packet in file coupling/ex4/PurpleZebra.java
class PurpleZebra {
public static int k;
//...
}
To use the above version of convertOzToMl(), a programmer
would have to do the following:
// In source packet in file coupling/ex4/Example4.java
class Example4 {
public static void main(String[] args) {
PurpleZebra.k = 16;
Liquid.convertOzToMl();
int mlFor16Oz = FlyingSaucer.q;
System.out.println("Ml for 16 oz is: " + mlFor16Oz);
}
}
To use this version of convertOzToMl(), client programmers
would have to know a lot about the internal implementation of the
method. They would have to know they must put their ounces into the
static variable PurpleZebra.k and grab the milliliters out
of FlyingSaucer.q. By contrast, the "good"
version of convertOzToMl() shown earlier in this article
enabled programmers to understand how to use it simply by looking at
the method's signature and return type. This "ugly" version,
because its signature and return type don't reveal all its inputs and
outputs, is harder to understand than the good version.
What's more, someone working on FlyingSaucer, not realizing
that q was being used elsewhere, might delete the variable
or use it for some other purpose. If q does get used for
some other purpose and the program is multithreaded, the value of
q could get trampled by a different thread after it is
assigned the ounces but before convertOzToMl() gets a
chance to use it.
This style of programming yields code that is difficult to change
because changes can have unforeseen side-effects. In this example, the
convertOzToMl() method has a high degree of coupling. It
is coupled to two classes, FlyingSaucer and
PurpleZebra, which are required for passing data to and
from the method. In Figure 4, you can see a graphical depiction of this
kind of programming:
Photo: Tom Prionas Figure 4. Please don't put spaghetti in your computer |
|
Sponsored Links
|