I have a simple two-class hierarchy to represent U.S. ZIP (12345) and ZIP+4 (12345-1234) codes. To allow clients to allow both types for a field/parameter or restrict it to one type or the other, the specific types inherit from a common generic ZipCode
interface.
Update: A ZIP code is five digits. A ZIP+4 code consists of the primary five-digit ZIP code plus a four-digit plus-4 (+4) code. You can create a ZIP+4 from a regular ZIP and get the primary ZIP from a ZIP+4. Thus, the interface which the two classes implement knows about the two classes, and they know about each other.
interface ZipCode boolean isPlusFour() ZipPlusFour plusFour(String code) Zip primary() boolean hasSamePrimary(ZipCode other) class Zip implements ZipCode String code class ZipPlusFour implements ZipCode Zip primary String plusFourCode
Introducing Null Object Pattern
To avoid duplicating code that checks for null values throughout the application, I would like to introduce the Null Object pattern. However, I'm afraid the only way to do so that supports the features above is to add three new classes instead of just one:
class NullZipCode implements ZipCode class NullZip extends Zip class NullZipPlusFour extends ZipPlusFour
Worse, since the last two extend concrete classes they will need to pass special values to their superclass that will pass validation but not block the possibility of using real values. For example, NullZip
would call super("00000")
.
Here are my main questions, though please don't hesitate to throw out any suggestions you have.
- Is there a way to solve this with just one new class to cover all bases?
Is it worth extracting interfaces from
Zip
andZipPlusFour
so thatNullZip
won't extend the concreteZip
implementation (same forZipPlusFour
)?ZipCode Zip NullZip RealZip
Another option is to forego separate classes altogether and check for a special value in
Zip
andZipPlusFour
to signify a missing ZIP code.class Zip boolean isNone() { return code.equals("00000"); } boolean hasSamePrimary(ZipCode other) { if (isNone() || other.isNone()) return false; else return code.equals(other.primary().code); }
At least the complicated checks are encapsulated in these classes which is the whole point of introducing the pattern. The downside is that this logic can be more fragile than inheritance. Is this a good trade-off?
ZipPlusFour
is composed of aZip
(the primary) and a four-digit +4 code. You can get a ZIP+4 from a ZIP by adding a +4 code, and you can get the primary ZIP from a full ZIP+4. I'll explain this more in the question.\$\endgroup\$HasPlusFour()
function to differentiate them, and aToString()
which returns the appropriate string representation depending on whether the +4 is there or not.\$\endgroup\$Customer
has aZip
since we do not store the +4 if provided when parsing, butDealer
has aZipCode
because it may be either a ZIP or ZIP+4 code.Phone
, on the other hand, accepts an optional extension using a single class as you describe.\$\endgroup\$boolean isPlusFour()
? Can we see some example code?\$\endgroup\$