Question

The situation:

I have a class hierarchy that looks a little bit like this. Note, my doesn't really look like this, I just wrote it this way to make it shorter and, in my opinion, more legible.

public abstract BaseClass {
    private Alpha a;
    private Beta b;

    public Alpha getAlpha() { return a; }
    public Beta getBeta() { return b; }
    public BaseClass setAlpha(Alpha a) { this.a = a; return this; }
    public BaseClass setBeta(Beta b) { this.b = b; return this; }
}

public Foo extends BaseClass {
    @Override
    public Foo setBeta(Beta b) {
        super.setBeta(b);
        doSomethingElse();
        return this;
    }
    public Gamma getGamma() { return g; }
    public Foo setGamma(Gamma g) { this.g = g; return this; }
}

public Bar extends BaseClass {
    private Omega o;
    @Override
    public Bar setAlpha(Alpha a) {
        super.setAlpha();
        doSomethingElse();
        return this;
    }
    public Omega getOmega() { return o; }
    public Bar setOmega(Omega o) { this.o = o; return this; }
}

The problem:

There are many more of these methods then listed above. Notice that they don't conform to the same interface. The classes that use Foo and Bar and need to call setGamma and setOmega always have access to the most specific class. However, those same classes also need to call setAlpha and setBeta. Those methods don't return the correct type, however, unless I override them.

Options:

  1. Override every method of BaseClass just to change the return type
    • Seems a lot of unnecessary code
  2. Declare every method like setGamma and setOmega abstract in BaseClass. This might work, except then I'd have to define it for classes where it really makes no sense at all.
  3. Similar to #2, except I don't declare these methods abstract, I give them an implementation - throw new UnsupportedOperationException()
  4. Similar to #3, except I just ignore the problem rather than throwing an Exception.
  5. Always all my builders with the most specific return types first. i.e. someBar.setOmega().setAlpha() but not someBar.setAlpha().setOmega()

All of these options seem gross. Does anyone have anything better?

Note: this situation is not quite like this one: Java - Inherited Fluent method return type to return incident class' type, not parent's as it's not really a generics issue.

Was it helpful?

Solution

As @chrylis noted in the comments, you can genericize BaseClass:

public abstract class BaseClass<T extends BaseClass> {
    private Alpha a;
    private Beta b;

    public Alpha getAlpha() {
        return a;
    }

    public Beta getBeta() {
        return b;
    }

    @SuppressWarnings("unchecked")
    public T setAlpha(Alpha a) {
        this.a = a;
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    public T setBeta(Beta b) {
        this.b = b;
        return (T) this;
    }
}

and extend as follows:

Foo:

public class Foo extends BaseClass<Foo> {
    private Gamma g;

    @Override
    public Foo setBeta(Beta b) {
        super.setBeta(b);
        doSomethingElse();
        return this;
    }

    private void doSomethingElse() { }

    public Gamma getGamma() {
        return g;
    }

    public Foo setGamma(Gamma g) {
        this.g = g;
        return this;
    }
}

Bar:

public class Bar extends BaseClass<Bar> {
    private Omega o;

    @Override
    public Bar setAlpha(Alpha a) {
        super.setAlpha(a);
        doSomethingElse();
        return this;
    }

    private void doSomethingElse() { }

    public Omega getOmega() {
        return o;
    }

    public Bar setOmega(Omega o) {
        this.o = o;
        return this;
    }
}

This introduces a couple unchecked cast warnings, which I suppressed. It compiles, but I haven't tried actually doing anything with it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top