1

I'm designing a class that holds several types of data. Some of the properties are optional. For example, let's say I have a class that represents a person, and one of the properties is occupation. I have another property, military rank, but this property is can only be retrieved if the person's occupation is military. What's the best way to handle this?

  1. Have getMilitaryRank() throw IllegalStateException if the occupation isn't military
  2. Have getMilitaryRank() return an Optional<Rank>
  3. Return null if the occupation is invalid (I don't like nulls)
  4. Something else?

Idea 1 works because the user can check the occupation prior to calling this method, making sure they don't call it on an invalid person.

Idea 2 seems unnecessary, because you can infer whether the Optional is present by calling getOccupation().

However, let's say my Person class has another optional property, but its presence can't be inferred from the value of another property; for example, a middle name. Obviously it's best for getMiddleName() to return an empty Optional or null if the person has no middle name, as the presence of a middle name can't really be inferred some other way.

Since I should probably be consistent in how I'm handling optional properties, it seems like I could do one of the following:

  1. Use Optional for any property that may be absent, even if it can be inferred from another property.
  2. Add methods like hasMiddleName() to resolve the discrepancies (this seems like a bad idea)
  3. Not be consistent and let the properties whose presence can be determined externally throw an exception, while the ones that are independent be wrapped in an Optional.
  4. Something else?

While nulls could simplify things, it causes a problem if an optional property is an int, which can't be nullable in Java. I could use an Integer, but that seems like a bad idea.

What is the best way to handle these different types of optional properties?

codebreaker
  • 1,754

3 Answers3

2

There's two solutions, depending on the specifics of your problem. If these optional properties are only combined in a relatively small number of ways, you can use a sum type to distinguish between them. The advantage of this approach is that you can guarantee exhaustiveness - you can statically ensure that wherever a decision must be taken based on the particular kind of person, all of the kinds are handled somehow.

The other solution is to group the optional properties that always occur together into interfaces and then implement the interfaces you can support in each class. You can detect the particular interfaces a person supports by applying instanceof and then casting. This approach won't force you to update all the use sites when you add a new interface, but works better when the number of possible combinations of optional properties is very large. E.g. if there's 7 groups of optional properties, there's 127 ways to combine those groups into classes, and it'd be ridiculous to take a decision based on each possible combination. If you need to reuse the implementations of those interfaces, use composition.

That aside, do be careful about making assumptions about people's names.

Doval
  • 15,487
1

I like DFord idea of inheritance...by extending the Person class... here are some additional ideas to consider...

public class Person {

  private String middelName;
  private String occupation;

  public String getMiddleName() {
      if (middleName == null) {
         return ""; // or any default value
      } else {
         return middleName;
      }
  }

  public String getOccupation() {
     if (occupation == null) {
         return "Unknown";
     } else {
         return occupation;
     }
  }

  public class Military extends Person {
     String rank;
     public String getMilitaryRank() {
        return rank;
     }
  }

....then in the code somewhere...

if (person instanceof Military) {
   String rank = person.getMilitaryRank();
}

Another idea is to create an enum for Occupation and have it return the default value of the Occupation if nothing is set, instead of "unknown". Sometimes checking for specific values like the text "unknown" can add extra complexity.

 if (occupation == null) {
    return Occupation.UNKNOWN;
 } else {
    return occupation;
 }

... a better option is to initialize all of the variables in the constructor

public class Person {
    public Person() {
       middleName = "";
       occupation = Occupation.UNKNOWN;
    }

    public Occupation getOccupation() {
       return occupation;
    }

    public String getMiddleName() {
       return middleName;
    }
Brian
  • 121
-1

Why not create a sub-class which represents someone in the military and have it inherit from your parent class. That way, you can check the type of class the object is. If its the Military class, you can call getMilitaryRank().

You can then create additional classes for other situations similar to this.

DFord
  • 1,250