Null Object pattern with simple class hierarchy
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