Java: возврат подкласса в сигнатуре метода суперкласса

Я работаю над проблемой, когда есть несколько реализаций Foo, сопровождаемых несколькими FooBuilder. Хотя Foo имеют несколько общих переменных, которые необходимо установить, у них также есть отдельные переменные, которые требуют соответствующих FooBuilder для реализации некоторых специфических функций. Для краткости я бы хотел, чтобы сеттеры FooBuilder использовали цепочку методов, например:

public abstract class FooBuilder {
  ...

  public FooBuilder setA(int A) {
    this.A = A;
    return this;
  }

  ...
}

а также

public class FooImplBuilder extends FooBuilder{
  ...
  public FooImplBuilder setB(int B) {
    this.B = B;
    return this;
  }
  public FooImplBuilder setC(int C) {
    this.C = C;
    return this;
  }
  ...
}

И так далее, с несколькими различными реализациями FooBuilder. Технически это делает все, что я хочу, однако этот подход чувствителен к порядку вызовов методов при выполнении цепочки методов. Следующее имеет метод неопределенных ошибок компиляции:

someFoo.setA(a).setB(b)...

Требование, чтобы разработчик думал о порядке вызовов методов в цепочке. Чтобы избежать этого, я хотел бы, чтобы сеттеры в FooBuilder каким-то образом возвращали фактический реализующий подкласс. Однако я не уверен, как это сделать. Каков наилучший подход?


person downer    schedule 10.06.2012    source источник
comment
Кроме того, он либо принудительно выполняет кастинг везде, либо не позволяет вам изменять свойства суперклассов, когда вы хотите.   -  person Mark Jeronimus    schedule 20.10.2014


Ответы (3)


Это хороший вопрос и реальная проблема.

Самый простой способ справиться с этим на Java, вероятно, включает использование дженериков, как упоминалось в ответе Йохена.

В этой записи блога на странице Использование наследования с плавными интерфейсами, которое сочетает в себе дженерики с определением метода getThis(), переопределенного в подклассах построителей, чтобы решить проблему постоянного возврата построителя правильного класса.

person Don Roby    schedule 10.06.2012
comment
Мне нравится это решение. Гораздо более сложный, чем то, что я предложил, но также более гибкий и, в конечном счете, элегантный. - person Jochen; 10.06.2012
comment
Просто подытожим связанный блог и удалим отдельные построители: public abstract class X<B extends X<?>>{public int a;public B setA(int foo){this.a=foo;return getThis();}public abstract B getThis();}public class Y extends X<Y>{public int b;public Y setB(int bar){this.b=bar;return getThis();}public Y getThis(){return this;}} - person Mark Jeronimus; 20.10.2014

Найдя отличный ответ, я делюсь им со всеми.

public class SuperClass<I extends SuperClass>
{
    @SuppressWarnings( "unchecked" ) // If you're annoyed by Lint.
    public I doStuff( Object withThings )
    {
        // Do stuff with things.
        return (I)this ; // Will always cast to the subclass. Causes the Lint warning.
    }
}

public class ImplementationOne
extends SuperClass<ImplementationOne>
{} // doStuff() will return an instance of ImplementationOne

public class ImplementationTwo
extends SuperClass<ImplementationTwo>
{} // doStuff() will return an instance of ImplementationTwo
person zerobandwidth    schedule 01.03.2017

Здесь могут подойти дженерики.

Если вы объявите setA() примерно так (псевдокод)

<T> T setA(int a)

компилятор должен уметь определять настоящие типы, а если нет, вы можете давать подсказки в коде, например

obj.<RealFoo>setA(42)
person Jochen    schedule 10.06.2012
comment
+1: хорошее описание этой тактики можно найти на странице egalluzzo.blogspot. .com/2010/06/ - person Don Roby; 10.06.2012
comment
Компилятор не может определить тип при вызове связанного метода, и повторение типа для каждого вызова метода вряд ли возможно. - person meriton; 10.06.2012
comment
@DonRoby: Почему бы вам не опубликовать это как ответ? Это лучшее решение, чем у Йохена, и оно, безусловно, заслуживает одобрения ;-) - person meriton; 10.06.2012