HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Null Object pattern with simple class hierarchy

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplewithnullhierarchyobjectclasspattern

Problem

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 and ZipPlusFour so that NullZip won't extend the concrete Zip implementation (same for ZipPlusFour)?

ZipCode
  Zip
    NullZip
    RealZip


-
Another option is to forego separate classes altogether and check for a special value in Zip and ZipPlusFour

Solution

This is a preliminary answer:


Assume for the sake of this question that going that route isn't
practical.

Assuming this is about some group of similar value objects.
Everything is meaningful in a context. So we need to determine what are the use cases for this object. Suppose we have the following use cases (I'm trying to guess how you could end up with the provided interfaces.):

  • We need to display a ZipCode on screen



  • We need to print ZipCode on envelopes



  • We also need to lookup some external service for demographic data using the 5-digit ZIP as a key.



Simplest thing that could work would then be something like:

interface ZipCode
    Zip primary() // for service lookup
    String getScreenRepresentation()
    String getPrintRepresentation()
    // you may one to use Visitor pattern 
    // if you have much more different formats


Now we may try to design a Null Object that could work for all these situations.
for ZIP and ZIP+4 their implementations are straight forward.
For NullZipCode we can have getScreenRepresentation to return "No ZIP provided" and
getPrintRepresentation to return empty string. You definitely would not want "00000" or some other invalid value on the label. (Unless in the improbable case that US postal service requires people put it to designate unknown ZIP, but you get what I mean)

My criticism of the below code

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


isPlusFour is the back door where all the null checks sneak back in.

plusFour(code) does not belong in the interface. It is a factory method. One sign it does not belong there is Zip and ZipPlusFour should not be able to provide different versions.

hasSamePrimary is unnecessary. Value objects should provide their equality, comparison, hash, display string etc. by overriding equals, hashCode etc in Java, (or deriving Show, Eq in Haskell and so on). By value object semantics; zipCode1.hasSamePrimary(zipCode2) should be equivalent to zipCode1.primary().equals(zipCode2.primary()), it is not an overridable behavior (Closed for modification principle). If it is there for brevity, it can be placed in an abstract base class or as a static method in a Utility class (or extension method if you are using C#, etc.)

What you should do for Dealer class cannot be a simple interface, if it is to be a value object also.

class ZipOrZipPlusFour implements ZipCode
     //fields depend on what sort of persistence you use
     // factory methods:
     newNullZipCode()
     newZip(String code)
     newZipPlusFour(Zip primary, String ext)

class Dealer
    ZipOrZipPlusFour zipCode

Code Snippets

interface ZipCode
    Zip primary() // for service lookup
    String getScreenRepresentation()
    String getPrintRepresentation()
    // you may one to use Visitor pattern 
    // if you have much more different formats
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
class ZipOrZipPlusFour implements ZipCode
     //fields depend on what sort of persistence you use
     // factory methods:
     newNullZipCode()
     newZip(String code)
     newZipPlusFour(Zip primary, String ext)

class Dealer
    ZipOrZipPlusFour zipCode

Context

StackExchange Code Review Q#24869, answer score: 2

Revisions (0)

No revisions yet.