patternjavaMinor
Null Object pattern with simple class hierarchy
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
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.
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:
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,
Here are my main questions, though please don't hesitate to throw out any suggestions you have.
-
Is it worth extracting interfaces from
-
Another option is to forego separate classes altogether and check for a special value in
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 plusFourCodeIntroducing 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 ZipPlusFourWorse, 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 ZipPlusFourSolution
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.):
Simplest thing that could work would then be something like:
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
My criticism of the below code
What you should do for
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 formatsNow 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 plusFourCodeisPlusFour 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 zipCodeCode 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 formatsinterface 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 plusFourCodeclass 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 zipCodeContext
StackExchange Code Review Q#24869, answer score: 2
Revisions (0)
No revisions yet.