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,694
  • 1
  • 18
  • 24
  • bad programmers copy, great programmers steal. Steal from the way how [JCF](http://docs.oracle.com/javase/tutorial/collections/interfaces/index.html "Collections Framework") handles properties of collections being modifiable or not: "modification operations in each interface are designated optional — a given implementation may elect not to support all operations. If an unsupported operation is invoked, a collection throws an UnsupportedOperationException. Implementations are responsible for documenting which of the optional operations they support..." – gnat Apr 23 '14 at 22:19
  • Well, that works for methods, but these aren't really methods, they're fields. The issue is, I need these fields to be optional for any given instance of the class, not be optional for any given implementation of the class. – codebreaker Apr 23 '14 at 22:57
  • @gnat I feel that's a terrible solution; it undermines the type system in the same way null does. An interface should only expose the operations it allows; start breaking that rule and you start sliding down a slippery slope of dynamic typing. – Doval Apr 24 '14 at 00:23
  • @codebreaker using fields is a major mistake in your design. Instead, use getter methods if you're dealing with DTO and _tell, don't ask_ approach otherwise – gnat Apr 24 '14 at 06:19
  • @Doval successful use of collections framework makes your statement hard to believe. Approach like this could probably be a wrong solution in a language like Eiffel; for Java it's quite idiomatic – gnat Apr 24 '14 at 06:23
  • @gnat `Goto` saw quite a bit of successful use, and `null` is still alive and kicking, but that doesn't make either of those concepts good ideas. I don't fault the Java API designers for it - OOP best practices were much less established at the time and they probably had deadlines to meet. But you can't deny that every additional use of "this may or may not be implemented" moves you one step closer to just declaring the variable `Object`. I'm all for being idiomatic, but that should come second to correctness. – Doval Apr 24 '14 at 11:57
  • @Doval relaxed attitude towards "OOP best practices" as you call them isn't anything archaic for Java API designers, it is present (and yes, idiomatic) nowadays, and I think it isn't going to go away anytime soon. As an example, refer ["types bloat" argument](http://programmers.stackexchange.com/a/218751/31260) used to justify particular design decision for Java 7 in 2009 – gnat Apr 24 '14 at 12:11
  • @gnat I understand that bad coding isn't going away any time soon. I also understand Java is full of bad ideas. It's a sad state of affairs that the language didn't have something resembling first-class functions until 2014. But that's completely besides the point. If you promote bad practices simply because they're idiomatic, all you have is cargo cult programming. Promoting a pattern *purely* because it's easily recognized by others is how anti-patterns stay alive for decades. – Doval Apr 24 '14 at 12:19
  • When I said they're not methods, they're fields, well, they are methods, they're getter methods, but they're just returning fields. – codebreaker Apr 24 '14 at 14:07

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,347
  • 3
  • 43
  • 58
  • Having multiple interfaces isn't a bad idea. If I wanted to have a mutable implementation of my Person class, though, this wouldn't work. Also, the name thing is pretty interesting. I'll check that out. The middle name thing was just an example, though, I was trying to come up with something that a person may optionally have exactly one of. – codebreaker Apr 24 '14 at 00:55
  • Why does mutability rule out interfaces? Are you saying you need to change whether an optional property is present or not at runtime? You could use the decorator pattern for that. – Doval Apr 24 '14 at 00:58
  • Well, that is true, but things could get messy if an object has to be decorated multiple times (or un-decorated). If possible I'd like to not get into using the runtime class of an object to determine if properties exist, and just stick with optionals/ nulls. My main question is, should I care about being consistent with throwing exceptions vs. returning some kind of absent value if the presence of some, but not all properties can be inferred? – codebreaker Apr 24 '14 at 01:13
  • @codebreaker You should strive to make invalid states impossible. For an approach using `Optional` or exceptions I recommend that you get and set entire groups of related properties at once. E.g. If the presence of `militaryRank` always implies the presence of `commandingOfficer`, then you should get or set both of those simultaneously, so the type system protects you from the illegal state where you ended up with one but not the other. – Doval Apr 24 '14 at 02:00
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
  • 3
-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,240
  • 2
  • 15
  • 30
  • 2
    The problem with that is Java doesn't have multiple inheritance, so this would work to some degree, but any time I have objects that need to have two of these properties, it gets messy. Otherwise, yes, this would probably be the best way. – codebreaker Apr 23 '14 at 22:53