|
|
|
Advertisement
|
Summary
This installment of the Design Techniques column shows how some fundamental software design techniques, like avoiding special data values and minimizing method coupling, apply to Java.
This month's installment of Design Techniques is the second in a mini-series of columns about designing objects. In last month's column, which covered designing objects for proper initialization, I talked about how to design constructors and initializers. This month and next month I'll discuss design principles for the actual fields and methods of the class. After that, I'll write about finalizers and show how to design objects for proper cleanup at the end of their lives.
The material for this article (avoiding special data values, using constants, minimizing coupling) and the next article (maximizing cohesion) may be familiar to many readers, as the material is based on general design principles that are quite independent of the Java programming language. Nevertheless, because I have encountered so much code over the years that doesn't take advantage of these principles, I think they deserve to be restated from time to time. In addition, in this article I attempt to show how these general principles apply to the Java language in particular.
Designing fields
In designing fields, the main rule of thumb is to avoid using one
variable to represent multiple attributes of a class. You can violate
this rule by denoting special values within a variable, each with its
own special meaning.
As used here, an attribute is a distinguishing characteristic
of an object or class. Two attributes of a CoffeeCup object, for
example, could be:
To take a closer look at this rule, imagine you are designing a CoffeeCup
class for the virtual
café described in last month's Design
Techniques column. Assume you want to model whether or not a
coffee cup in your virtual café has been washed and is ready for
use by the next customer. With this information on hand, you can ensure that
you don't reuse a coffee cup before it has been washed.
If you decide you only care whether or not a cup has been washed if it
is empty, you could use a special value of the innerCoffee
field, which normally is used to keep track of the amount of coffee in
the cup, to represent an unwashed cup. If 473 milliliters (16 fluid
ounces) is the maximum amount of coffee in your largest cup, then the
maximum value of innerCoffee normally would be 473. Thus,
you could use an innerCoffee value of, say, 500 (a special
value) to indicate an empty cup that is unwashed:
// In source packet in file fields/ex1/CoffeeCup.java
class CoffeeCup {
private int innerCoffee;
public boolean isReadyForNextUse() {
// If coffee cup isn't washed, then it's
// not ready for next use
if (innerCoffee == 500) {
return false;
}
return true;
}
public void setCustomerDone() {
innerCoffee = 500;
//...
}
public void wash() {
innerCoffee = 0;
//...
}
// ...
}
This code will give CoffeeCup objects the desired
behavior. The trouble with this approach is that special values aren't
readily understood, and they make code harder to change. Even if you
describe special values in a comment, it may take other programmers
longer to understand what your code is doing. Moreover, they may never
understand your code. They may use your class incorrectly or change it
such that they introduce a bug.
For example, if later someone adds a 20 ounce cup to the offerings of the virtual café, it would then be possible to hold up to 592 milliliters (ml) of coffee in a cup. If a programmer adds the new cup size without realizing you are using 500 ml to indicate that a cup needs washing, it is likely that a bug will be introduced. If a customer in your virtual café bought a 20 ounce cup, then took a big 92-ml gulp, he or she would then have exactly 500 ml remaining in the cup. The customer would be shocked and dissatisfied when, after drinking only 92 ml, the cup disappeared from his or her hand and appeared in the sink, ready to be washed. And, even if the programmer making the change realized that you were using a special value, another special value for the unwashed attribute would have to be chosen.
A better approach to this situation is to have a separate field to model the separate attribute:
Here the innerCoffee field is used only to model the
amount of coffee in the cup attribute. The cup-needs-washing attribute
is modeled by the needsWashing field. This scheme is more
easily understood than the previous scheme, which used a special value of
innerCoffee and wouldn't prevent someone from expanding the
maximum value for innerCoffee.
|
Sponsored Links
|