Существует ли предполагаемая ковариация - PullRequest
0 голосов
/ 19 мая 2018

Я выполняю рефакторинг некоторого кода в шаблоне компоновщика и сталкиваюсь с проблемой при создании подкласса компоновщика для дочернего класса.

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

public class App {

    public static void main(String[] args) {
        Parent p;
        p = new App().new ChildBuilder()
            .withName("Test")
            .withNickName("Test1")
            .build();// Doesn't Compile
        p = new App().new ChildBuilder()
            .withNickName("Test1")
            .withName("Test")
            .build();

    }

    class Parent {
        public Parent(ParentBuilder builder) {}
    }

    class Child extends Parent {
        public Child(ChildBuilder builder) { super(builder); }
    }

    class ParentBuilder {
        private String name;
        public ParentBuilder() {}

        public Parent build() { return new Parent(this); }

        public ParentBuilder withName(String name) { 
            this.name = name; 
            return this; 
        }
    }

    class ChildBuilder extends ParentBuilder {
        private String nickName;
        public ChildBuilder withNickName(String nickName) { 
            this.nickName = nickName; 
            return this; 
        }
    }
}  

Вторая строка в методе main не будет компилироваться, поскольку withName("Test") находится в классе ParentBuilder и возвращает ParentBuilder.Переупорядочение цепочек для вызова всех методов ChildBuilder сначала решает проблему, но это звучит как ужасный опыт для людей, использующих мой API (включая меня).
Если я добавлю переопределение в ребенка, я смогу заставить его работать через ковариацию:

        @Override
        public ChildBuilder withName(String name) { 
            super.withName(name); 
            return this; 
        }

Но это много стандартного кода, который я бы предпочел не поддерживать (у каждого родительского компоновщика может быть несколько подклассов, поэтому мне нужно было бы переписать эти методы в каждом подклассе для каждого метода вродительский класс).

Есть ли способ сделать то, что я пытаюсь сделать без переопределений?Может ли Java "вывести" ковариантные методы у детей?

Я также обеспокоен тем, что эта проблема свидетельствует о том, что я неправильно проектирую строителей.

1 Ответ

0 голосов
/ 19 мая 2018

Нет, нет предполагаемой co-дисперсии, но она имитируется любопытно повторяющимся шаблоном (или CRTP, созданным в C ++).

Вы можете решить эту проблему, добавив 2 (package private) абстрактных классакоторые используют CRTP (т. е. тип параметра является подклассом).Функциональность компоновщика перемещается в эти классы, и затем вы создаете 2 пустых класса, расширяющих абстрактные компоновщики.

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

public static void main(String[] args) {
    // Both examples now compile
    Parent p;
    p = new App().new ChildBuilder()
        .withName("Test")
        .withNickName("Test1")
        .build();
    p = new App().new ChildBuilder()
        .withNickName("Test1")
        .withName("Test")
        .build();

}

class Parent {
    public Parent(String name) {}
}

class Child extends Parent {
    public Child(String name, String nickName) { super(name); }
}

abstract class AbstractParentBuilder<T extends AbstractParentBuilder<T>> {
    protected String name;
    protected AbstractParentBuilder() {}

    public Parent build() { return new Parent(name); }

    @SuppressWarnings("unchecked")
    public T withName(String name) {
        this.name = name; 
        return (T) this; 
    }
}

class ParentBuilder extends AbstractParentBuilder<ParentBuilder> {}

abstract class AbstractChildBuilder<T extends AbstractChildBuilder<T>> extends AbstractParentBuilder<T> {
    protected String nickName;
    protected AbstractChildBuilder() {}        

    public Child build() { return new Child(name, nickName); }

    @SuppressWarnings("unchecked")
    public T withNickName(String nickName) { 
        this.nickName = nickName; 
        return (T) this; 
    }
}

class ChildBuilder extends AbstractChildBuilder<ChildBuilder> {}
...