Подтвердить что ты не робот

Возвращать класс child из родительского класса

У меня есть класс-строитель, который возвращает себя из большинства методов, чтобы обеспечить цепочку. Чтобы сделать эту работу с дочерними классами, я хочу, чтобы родительские методы возвращали экземпляры дочернего элемента, чтобы дочерние методы были доступны для цепочки до конца.

public class BaseBuilder<T extends BaseBuilder<T>> {
    public T buildSomething() {
        doSomeWork();
        /* OPTION #1: */ return this;     // "Type mismatch: cannot convert from BaseBuilder<T> to T"
        /* OPTION #2: */ return T.this;   // "Type mismatch: cannot convert from BaseBuilder<T> to T"
        /* OPTION #3: */ return (T) this; // "Type safety: Unchecked cast from SqlBuilder<T> to T"
    }
}

public class ChildBuilder extends BaseBuilder<ChildBuilder> {}

Параметры # 1 и # 2 приводят к ошибкам компиляции, а опция № 3 - предупреждение (хотя оно может быть подавлено с помощью @SuppressWarnings("unchecked")). Есть ли лучший подход здесь? Как я могу безопасно отключить Basebuilder для Childbuilder?

4b9b3361

Ответ 1

Объявление ChildBuilder extends BaseBuilder<ChildBuilder> как-то указывает на запах кода и, кажется, нарушает DRY. В этом примере BaseBuilder можно параметризовать только с помощью ChildBuilder и больше ничего, поэтому он должен быть избыточным.

Я предпочел бы переосмыслить, действительно ли я хочу переопределить эту архитектуру, и постараюсь поместить все методы из дочерних сборщиков в BaseBuilder. Затем я могу просто вернуть this из всех методов поддержки цепочки.

Если я по-прежнему думаю, что мне удастся отделить определенные группы методов построения в своих собственных классах, тогда я бы отдали предпочтение композиции, потому что применение наследования только для повторного использования кода не рекомендуется.

Предположим, что у нас есть два подкласса BaseBuilder:

class BuilderA extends BaseBuilder<BuilderA> {
   BuilderA buildSomethingA() { return this; }
}

class BuilderB extends BaseBuilder<BuilderB> {
   BuilderB buildSomethingB() { return this; }
}

Что делать, если возникает необходимость в цепочке buildSomethingA и buildSomethingB, например:

builder.buildSomething().buildSomethingA().buildSomethingB();

Мы не сможем сделать это, не переместив методы подкласса на BaseBuilder; но представьте, что существует также BuilderC, для которого эти методы не имеют смысла и не должны унаследоваться от BaseBuilder.

Если мы все же переместим эти два метода в суперкласс, а в следующий раз три других метода и в следующий раз... мы получим суперкласс, отвечающий за 90% обязанностей всей иерархии с большим количеством кода, например

if ((this instanceof BuilderB) && !flag1 && flag2) {
   ...
} else if ((this instanceof BuilderC) && flag1 && !flag2 && thing != null) {
   ...
} else if ...

Решение, которое мне больше нравится, - это DSL:

builder.buildSomething1().buildSomething2()
   .builderA()
      .buildSomethingA1().buildSomethingA2()
   .end()
   .buildSomething3()
   .builderB()
      .buildSomethingB()
   .end();

Здесь end() возвращает экземпляр builder, чтобы вы могли связать больше своих методов или запустить новый подзатворщик.

Таким образом, разработчики (суб) могут наследовать все, что им нужно (в противном случае они должны распространяться только на BaseBuilder), и могут иметь свои собственные значимые иерархии или композиции.

Ответ 2

Одна из возможностей - использовать тот факт, что Java поддерживает ковариантные типы возвращаемых данных. Например, этот код является законным:

class BaseBuilder {
    BaseBuilder buildSomething() { (...) return this; }
}

class ChildBuilder extends BaseBuilder {
    @Override  // Notice the more specific return type
    ChildBuilder buildSomething() { (...) return this; }
}

void main() {
    BaseBuilder  x = new BaseBuilder ().buildSomething().anotherOperation();
    ChildBuilder y = new ChildBuilder().buildSomething().anotherOperation();
}

В противном случае опция № 3 - это единственный способ добиться того, чего вы хотите. Он позволяет методам суперкласса напрямую возвращать тип подкласса, чтобы вы могли ссылаться на методы подкласса:

@SuppressWarnings("unchecked")   // Ugly.
class Base<T extends Base<T>> {  // Ugly.
    public T alpha() { return (T)this; }
    public T delta() { return (T)this; }
}

class Child extends Base<Child> {  // Clean.
    // No need to override/redefine alpha() and delta() in child.
    public Child gamma() { return this; }
}

void main(String[] args) {
    Child x = new Child();
    x.alpha().gamma();  // This works because alpha() returns Child.
}

Ответ 3

Включение опции №3 небезопасно, поскольку следующий класс будет компилироваться (это ответственность разработчика):

public class ChildBuilder extends BaseBuilder<FakeBuilder> {}
                                              ^^^^^^^^^^^

Общим решением является задание подклассов для их this:

public abstract class BaseBuilder<T extends BaseBuilder<T>> {
  protected abstract T getThis();
  public T buildSomething() {
    return getThis();
  }
}

public class ChildBuilder extends BaseBuilder<ChildBuilder> {
  @Override
  protected ChildBuilder getThis() {
    return this;
  }
}

Ответ 4

Вместо того, чтобы объявить метод для возврата T - объявить его возвратом BaseBuilder:

public BaseBuilder buildSomething() {
...

Так как T extends BaseBuilder - но до сих пор не известно во время компиляции, я считаю, что это лучший компромисс, который вы можете сделать.

Если вы знаете (во время компиляции) именно тот тип, который вы возвращаете, вы можете просто вернуть его, но если нет - и вам придется сбрасывать - вы будете продолжать получать "Type safety: Unchecked cast, и если вы сможете доказать, что downcast действителен, что отлично подходит для SuppressWarnings.

См. Josh Bloch мудрые слова в отношении.

Ответ 5

IMO, которую вы создаете подписи подписи BaseBuilder<T extends BaseBuilder<T>>, необходимо изменить.

Я бы предположил, что T ссылается на построенный тип BaseBuilder<T extends ComplexObject> и ChildBuilder extends BaseBuilder<MoreComplexObject>

Переопределение методов в ChildBuilder все еще работает - вы возвращаете this. Из метода сборки вы возвращаете T

public class BaseBuilder<T extends ComplexObject> {
   public BaseBuilder<T> withComplexThing() {
        return this;
   }

   public T build() {
   }

}

public class ChildBuilder extends BaseBuilder<MoreComplexObject> {
   public ChildBuilder withComplexThing() {
        return this;
   }

   public MoreComplexObject build() {
   }
}

Ответ 6

Это нормально, просто подавите предупреждение.

Если вы должны быть пуристом, вот решение:

abstract public class BaseBuilder<T...> 
{
    abstract protected T getThis();

    public T buildSomething() 
       ...
          return getThis();


...

public class ChildBuilder extends BaseBuilder<ChildBuilder> 
{
    @Override
    protected ChildBuilder getThis(){ return this; } 
}

Я бы рекомендовал перерезать рекурсивную границу; это в основном бесполезно. Просто введите переменную типа This.

public class BaseBuilder<This>