I've run into several situations where a POJO where whether a field value is meaningful depends on the value of another field. An example, using Lombok (which we try to use to avoid boilerplate):
@Data
@Builder
public class SomePojo {
// an enum whose values are DEFAULT_LOCATION, STANDARD_INPUT, FILE
private final SourceType sourceType;
// meaningful only if sourceType == FILE
private final String path;
private final String attributes;
}
(This is just an example; the case I'm now looking at has nothing to do with files. If it makes a difference, the class's purpose is to be the return type of a method that needs to return several pieces of information to the caller.)
Although the class could be used as is, what are the best practices for dealing with this situation? Specifically:
Should I write a custom getter method for
path
that throws an exception ifsourceType != FILE
?Should I write a custom builder that throws an exception if the builder tries to set
path
when it setssourceType
to something other thanFILE
?Should I write custom
equals()
andhashCode()
that don't look atpath
ifsourceType != FILE
?Should my builder or constructor set
path
to some fixed value ifsourceType != FILE
? Doing so would eliminate the need for specialequals()
andhashCode()
.Should I make path an
Optional<String>
? Would it be enough to do this without doing any of #1-4?Is it preferable to define a new class hierarchy to encapsulate the
sourceType
andpath
fields (so there would be three subclasses of some base class and only one of them would have apath
)? Let's assume that there aren't any polymorphic methods in this hierarchy.
MORE: I agree with the comment that if a method returns a variant record, there's a good chance that the method is doing too much, and that needs to be checked. But this isn't always the case. A very simple example would be a method that searches a string to see if it contains a substring, and returns the position of the substring if present. (Java's method to do this returns a special value as the "position" if the substring isn't present, which I'd argue is a bad practice because it can introduce errors if a caller fails to check this case and treats the resulting position as a number. Optional
would help in this case, but not in a case where there are more than two states to return.) This seems to be a natural use case for returning either (FOUND, position
) or (NOT_FOUND) which wouldn't have a position. The case I'm working with isn't quite that simple, but it's been examined thoroughly and has already been torn up a couple of times by colleagues in design reviews, so I'm pretty sure that it's not a method that does too much.