3
\$\begingroup\$

I've spent a couple of weeks implementing a simple text editor. The entire program uses only immutable objects and a functional, fluid programming style.

After reading about ways to implement Fluent Interfaces in Java I came to the conclusion there was no elegant solution. The language just can't handle it in a nice way. One of the main issues is trying to return the immutable object from bases class with() methods. If you just return 'this' then you get back the base class, not the concrete class that you need from calling code.

This is the system I am using in my code now. Let me know if there are ways of doing it in a less hackish, ugly way.

My main requirements are:

  • Use inheritance so common functionality doesn't need to get cut and paste into subclasses

  • Allow calling code to chain methods in a fluent style: object.withColor("red").withHeight(50) etc..

  • Avoid 3rd party libraries or source code builders

Code:

public abstract class Animal<TConcrete extends Animal<TConcrete>> {
    public final int height;

    protected Animal(int height) {
        this.height = height;
    }

    // Need to return 'this' sometimes in base methods but if you just use
    // keyword 'this' then the type is wrong (i.e. different than TConcrete).
    protected abstract TConcrete self();

    // Base classes normally can't instantiate the concrete class because it
    // might have extra constructors and members that the base cannot possibly
    // know
    // about.
    //
    // With this method you are able to construct a new object that has a copy
    // of all the subclass members but with a new base class member value.
    protected abstract TConcrete copy(int height);

    public TConcrete withHeight(int height) {
        return copy(height);
    }
}

public abstract class Mammal<TConcrete extends Mammal<TConcrete>> extends Animal<TConcrete> {
    // This is the only mandatory field for this whole hierarchy. Naming is just
    // to illustrate in this example.
    public final int furColorMandatory;

    // The constructor now takes 2 parameters, unlike Animal class which takes
    // just 1. It takes all the parameters of base classes plus the new member
    // 'furColor'.
    protected Mammal(int height, int furColorMandatory) {
        super(height);
        this.furColorMandatory = furColorMandatory;
    }

    // Now implement a new copy() method for the subclass to implement. The old
    // copy() from base class gets implemented below.
    //
    // This forms a chain where the base class is able to instantiate a copy
    // of the object by calling the copy method.
    protected abstract TConcrete copy(int height, int furColor);

    // Each class in the hierarchy needs to implement the copy()
    // method of the abstract class below. This allows the base
    // class to instantiate a concrete object.
    //
    // Note the current value for 'furColor' is used because this is a copy
    // operation as opposed to totally new construction of an object.
    @Override
    protected final TConcrete copy(int height) {
        return copy(height, furColorMandatory);
    }

    public TConcrete withFurColor(int furColorMandatory) {
        return copy(height, furColorMandatory);
    }
}

public final class Cat extends Mammal<Cat> {
    public final int numberOfWhiskers;

    protected Cat(int height, int furColorMandatory, int numberOfWhiskers) {
        super(height, furColorMandatory);
        this.numberOfWhiskers = numberOfWhiskers;
    }

    // Finally implement the concrete 'this' method used only by base classes.
    @Override
    protected Cat self() {
        return this;
    }

    protected Cat copy(int height, int furColorMandatory, int numberOfWhiskers) {
        return new Cat(height, furColorMandatory, numberOfWhiskers);
    }

    // This should be final but the class is final so keyword is not needed.
    @Override
    protected Cat copy(int height, int furColorMandatory) {
        return copy(height, furColorMandatory, numberOfWhiskers);
    }

    public Cat withNumberOfWhiskers(int numberOfWhiskers) {
        return copy(height, furColorMandatory, numberOfWhiskers);
    }

    // Only way to instantiate this for callers.  Ensures mandatory is taken care of.
    public static Cat of(int furColorMandatory) {
        return new Cat(0, furColorMandatory, 0);
    }
}

Usage: Cat tom = Cat.of(5).withHeight(10);

\$\endgroup\$
  • 1
    \$\begingroup\$ May I suggest using Kotlin or Scala instead? (Only partially sarcastic) \$\endgroup\$ – CAD97 Jul 16 '17 at 7:56
  • \$\begingroup\$ Kotlin in particular looks really nice, especially now i'm getting the functional programming bug from doing the above. I just don't think I have the time for this particular project to spend a few weeks getting used it unfortunately. \$\endgroup\$ – LegendLength Jul 16 '17 at 11:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.