A Pattern for Java Classes

I have settled on a pattern for implementing Java classes that fits my intuitive notion of order and taxonomy. I use two distinct classes for each logical data type: the main class and a “factory” for that class. Each of these classes has an interface. I should note that the factory is not a factory in the traditional Java sense, but an extension that I believe to be natural.

Consider a bounded semi-lattice1 data type. I would have two interfaces:

interface SemiLattice<ME> {
  ME join(ME t);

  boolean lte(ME t);
}

interface SemiLatticeFactory<T extends SemiLattice<T>> {
  T makeBottom();

  T joinCollection(Collection<T> ts);
}

I would implement this with two classes.

class FiniteSet<U> implements SemiLattice<FiniteSet<U>> {
  FiniteSet<U> join(U u) {
    // ...
  }

  boolean lte(U u) {
    // ...
  }

  static <U> FiniteSet<U> makeBottom(int max) {
    return new FiniteSet<>(max);
  }

  static <U> FiniteSet<U>
  joinCollection(int max, Collection<FiniteSet<U>> sets) {
    sets.stream().reduce(makeBottom(max), FiniteSet::join);
  }

  private final int max;
  private FiniteSet(int max) {
    this.max = max;
  }
}

class FiniteSetFactory<U> implements SemiLatticeFactory<FiniteSet<U>> {
  FiniteSet<U> makeBottom() {
    return FiniteSet.makeBottom(this.max);
  }

  FiniteSet<U> joinCollection(Collection<FiniteSet<U>> sets) {
    return FiniteSet.joinCollection(this.max, sets);
  }

  static <U> FiniteSetFactory<U> make(int max) {
    return new FiniteSetFactory(max);
  }

  private final int max;
  private FiniteSetFactory(int max) {
    this.max = max;
  }
}

I think this example shows off two key advantages of this style. We will explore these supposed advantages through an example. Suppose we had a third class that was parameterized by the type of SemiLattice. I’ve recently been working on static analysis, so let us consider a static analyzer.

class Analysis<ValueAbstraction implements SemiLattice<ValueAbstraction>> {

  Result analyze(Program p) {
    // ...
    this.vaFactory.makeBottom();
    // ...
  }

  // ...

  static <ValueAbstraction implements SemiLattice<ValueAbstraction>>
  make(SemiLatticeFactory<ValueAbstraction> vaFactory) {
    return new Analysis(vaFactory);
  }

  private final SemiLatticeFactory<ValueAbstraction> vaFactory;
  private Analysis(SemiLatticeFactory<ValueAbstraction> vaFactory) {
    this.vaFactory = vaFactory;
  }
}

Our static analyzer should be parametric on how we abstract concrete values. It need only concern itself with a value-abstraction’s semi-lattice structure, and perhaps the existence of some fixed-points.2

I want to stress that for each type parameter a class must accept an additional constructor argument which is a run-time realization of that type parameter.

The first advantage appears in the access to the bottom element of this semi-lattice. Without the factory pattern, we couldn’t access the bottom element. This is frustrating limitation of generics. Ideally we could give an interface “class methods”. In that case, the Analysis class could access the bottom element directly from the generic parameter.

// Fantasy Java
interface SemiLattice<ME> {
  // ...

  classmethod ME makeBottom();
}

class Analysis<ValueAbstraction implements SemiLattice<ValueAbstraction>> {
  // ...

  ValueAbstraction.makeBottom();
}

The second advantage comes from setting the maximum set size in the factory.

Analysis<FiniteSet<Integer>> a = Analysis.make(FiniteSetFactory.make(5));
a.analyze(myProgram);

This follows the principle that the Analysis is only concerned with the semi-lattice structure. Even if we could statically determine the type of semi-lattice, which would eliminate the first advantage, the analysis would need to be parameterized by the maximum size of finite sets.

Now our Analysis must maintain information relevant only to FiniteSet’s. This seems really wrong. If we ever change or abstract over FiniteSet’s we’ll need to find all of the relevant dependencies in the Analysis class. With factories, we’ve isolated those dependencies to the factory object.


  1. A bounded semi-lattice is just a semi-lattice that has a least element, often called bottom and written as

  2. Alas, proving that our semi-lattice has fixed points is probably impossible in Java’s type system. 

back