Pull to refresh

Возвращаем дочерний класс из родительского. Факультатив

Reading time 3 min
Views 22K
С какой целью это может понадобиться? Например, если хочется использовать "текучий интерфейс". Правда, если часть методов разместить в родительском классе, то для них не получится сделать полноценный «текучий интерфейс» — эти методы требуется приводить (кастовать) к дочернему.

Казалось бы, печаль да и только, но если очень хочется, то ведь можется. Поэтому, давайте слегка нарушим правило и «известим» родительский класс о его наследниках.

Итак, имеется исходное состояние (коммит):

1) интерфейс IChildParam, который знает о всех требуемых методах
2) родительский класс BaseClass
1) класс-наследник ChildClass
public interface IChildParam {
    int getBaseParam();

    int getChildParam();

    <T> T setBaseParam(int i);

    <T> T setChildParam(int i);
}
public abstract class BaseClass implements IChildParam {
    private int baseParam;

    public int getBaseParam() {
        return baseParam;
    }

    public BaseClass setBaseParam(int i) {
        this.baseParam = i;
        return this;
    }
}
public class ChildClass extends BaseClass  {
    private int childParam;

    public int getChildParam() {
        return childParam;
    }

    public ChildClass setChildParam(int i) {
        this.childParam = i;
        return this;
    }
}


А так же, напишем небольшой тестовый класс, в котором опишем три возможных варианта вызова методов setBaseParam и setChildParam:
public class ClassTest {

    public static final int BASE_PARAM  = 1;
    public static final int CHILD_PARAM = 2;

    @DataProvider(name = "data")
    public static Object[][] data() {

        final ChildClass item0 = (ChildClass) new ChildClass().setBaseParam(BASE_PARAM);
        item0.setChildParam(CHILD_PARAM);

        final ChildClass item1 = (ChildClass) new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);

        final ChildClass item2 = ((ChildClass) new ChildClass().setBaseParam(BASE_PARAM)).setChildParam(CHILD_PARAM);

        return new Object[][]{{item0}, {item1}, {item2}};
    }

    @Test(dataProvider = "data")
    public void testChildClass(final IChildParam item) throws Exception {
        Assert.assertEquals(item.getBaseParam(), BASE_PARAM);
        Assert.assertEquals(item.getChildParam(), CHILD_PARAM);
    }
}


И вот здесь-то и кроется загвозка в коде — вместо того, чтобы вызвать метод setChildParam сразу же после вызова метода setBaseParam, приходится предварительно делать приведение типа и только потом вызывать setChildParam. Да и в любом случае нам потребуется явное приведение типа к ChildClass

Что же, давайте исправлять это «недоразумение».

Для начала, изменим объявление интерфейса — вынесем Generic-тип с уровня методов на уровень интерфейса:
public interface IChildParam<T> {
    int getBaseParam();

    int getChildParam();

    T setBaseParam(int i);

    T setChildParam(int i);
}


Далее внесём изменения в объявление класса BaseClass, добавив ему Generic-тип «самого себя же»:
public abstract class BaseClass<T extends BaseClass> implements IChildParam<T> {
    private int baseParam;

    public int getBaseParam() {
        return baseParam;
    }

    public T setBaseParam(int i) {
        this.baseParam = i;
        return (T) this;
    }
}


И заключительный шаг изменений в основном коде — изменим объявление в дочернем классе:
public class ChildClass extends BaseClass<ChildClass>  {
    private int childParam;

    public int getChildParam() {
        return childParam;
    }

    public ChildClass setChildParam(int i) {
        this.childParam = i;
        return this;
    }
}


Теперь ничто не мешает нам переписать код в тесте с полноценным использованием «текучего интерфейса»:
было:
final ChildClass item0 = (ChildClass) new ChildClass().setBaseParam(BASE_PARAM);
item0.setChildParam(CHILD_PARAM);

final ChildClass item1 = (ChildClass) new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);

final ChildClass item2 = ((ChildClass) new ChildClass().setBaseParam(BASE_PARAM)).setChildParam(CHILD_PARAM);
стало:
final ChildClass item0 = new ChildClass().setBaseParam(BASE_PARAM);
item0.setChildParam(CHILD_PARAM);

final ChildClass item1 = new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);

final ChildClass item2 = new ChildClass().setBaseParam(BASE_PARAM).setChildParam(CHILD_PARAM);


Коммит с результатом

PS: Конечно же мы ничего не нарушили и родительский класс как ничего не знал, так и остался в неведении о своих наследниках, но… Но зато он теперь может вернуть из метода тот же тип, что у его наследника, что, собственно, и требовалось.

UPD.0: Добавил пример для комментария
UPD.1: Добавил пример для комментария
Tags:
Hubs:
+8
Comments 21
Comments Comments 21

Articles