An Illuminating Example of Inheritance vs. Composition in Object-Functional Programming

The Scenario

I’m filtering out a small number of objects from a collection in Java 8.  Nothing special there, I streamed the collection and used a Predicate. Done.  But then the collection moved out into a cloud database and I found myself pulling the entire collection over the wire to filter out a small set of objects. While the code still worked perfectly performance really suffered. Additionally, the collections where going to continue to be in both places so I needed to find a way to handle both well.

Taking Stock

The cloud database in question was Orchestrate.io and it offers server side filtering based on Lucene queries. So obviously that’s what I wanted to use to reduce the traffic over the wire.  I considered moving away from my Predicates and introducing a query abstraction that could be converted either to a Predicate or a Lucene query based on the collection location. But it felt like that was over designing the solution because both Predicates and Lucene queries are basically built up of comparisons and boolean operators so I felt there ought to be a way to convert one model directly to the other and I already had the Predicates in place.

The Goal

My Predicates tested field/value pairs in beans, and could be built up through the negate, and and or methods.  For example:

Predicate predicate = new BeanPredicate("lastname","Doe")
       .and(new BeanPredicate("firstname","John")
           .or(new BeanPredicate("firstname","Jane")));

From that I wanted to derive a Lucene query:

lastname:"Doe" AND ( firstname:"John" OR firstname: "Jane")

I decided that I could override the toString method to make the Lucene representation available there.

Iteration 1: Subclassing

Java predicated use lambdas to implement the negate,and,or operator tests, but I needed the toString method to change for all those operators as well.  So I went with subclasses for the various operators.  My code looked something like this example of the and operation:

class BeanPredicate implements Predicate {
  private final String label;
  private final String value;

  ...
  public BeanPredicate and(BeanPredicate other) {
    return new BeanPredicate() {
      public boolean test(Bean bean) {
        return super.test(bean) && other.test(bean);
      };
      public toString() {
        return super.toString() + " AND " + other.toString();
      };
    }
  }
}

This passed all tests but damn was it verbose and ugly.

Iteration 2: Composition With Functions

What my next refactoring did was to change the test and toString over to Functions and then have the different operations compose proper implementation:

class BeanPredicate implements Predicate {
  private final String label;
  private final String value;
  private final Function<Bean, Boolean> test;
  private final Function<BeanPredicate, String> toString;
  ...
 
  public BeanPredicate and(BeanPredicate other) {
    return new BeanPredicate(label, value,
        bean -> test.apply(bean) && other.test(bean),
        bp -> toString.apply(this) + " AND " + other.toString());
  }
}

This may not appear glaringly different but it has a number of advantages. The operation implementations are cleaner, easier to read, and do not involve an anonymous class. When you consider that the bulk of the code is in the operations, being cleaner there pays off.

Conclusion

With a Object-Functional language the general wisdom of applying composition over subclassing becomes even more true and can be applied in an even more clean and powerful way.  Particularly, one common draw back of composition, where you end up polluting the class interface with indirect references through the composite types is completely avoided when you’re simply assigning functions.  You can take a look at the final implementation here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s