Свободные сеттеры с наследованием в java

Недавно я увидел некоторый код (amazon hasoop code), который использует этот тип синтаксиса

Foo bar = new Foo().setX(10).setY(11);

Я думал, что это было мило, поэтому я решил уйти. мои функции setX() возвращали Foo вместо void и ставили return this; во всех них. это сработало хорошо. пока я не попробовал его с наследованием, что привело к некоторым результатам задержек.

Я приведу конкретный пример: у меня есть два класса, Location класс, который имеет два поля: x и y. и другой класс Location3D, который наследует от Location и добавляет третье поле z. все поля используют метод, описанный выше для своих сеттеров.

теперь я хочу создать новый экземпляр location3D и установить его поля, что происходит,

new Location3D().setZ(7).setY(6).setX(5)

работает

new Location3D().setX(7).setY(6).setZ(5)

нет.

не работает Я имею в виду, что то, что возвращается из setY(6), является объектом Location, а не объектом Location3D и поэтому не имеет метода setZ()!

после этого длинного ввода мой вопрос: может ли эта форма "стрингеров сеттера" работать с наследованием, не заставляя вызывающего объекта бросать объекты? если да, то как?

Кроме того, я уверен, что для этого лучше использовать термин "setter stringing", что это такое?

Ответ 1

Как вы указали, причина, по которой new Location3D().setX(7).setY(6).setZ(5) не работает, состоит в том, что setX() и setY() возвращают экземпляры Location, а не Location3D.

Вы можете обойти это с помощью дженериков (хотя решение не особенно красиво), добавив в тип Location общий тип типа:

public class Location<T extends Location<?>> {
    protected int x, y;

    @SuppressWarnings("unchecked")
    public T setX(int x) {
        this.x = x;
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    public T setY(int y) {
        this.y = y;
        return (T) this;
    }
}

Ваш подкласс Location3D затем установил бы себя как общий тип типа, чтобы суперкласс возвращал экземпляры Location3D вместо Location:

public class Location3D extends Location<Location3D> {
    protected int z;

    public Location3D setZ(int z) {
        this.z = z;
        return this;
    }
}

К сожалению, я не знаю, как избежать предупреждений, созданных суперклассом, поэтому аннотации @SuppressWarnings("unchecked").

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

Наконец, цепочка вызовов метода в том, как вы описываете, обычно называется цепочкой методов метода. Стиль методов setter, который вы описываете, тесно связан с шаблоном .

Ответ 2

Проблема в том, что Location возвращает местоположение, так как Location3D вернет Location3D. Чтобы решить эту проблему, переопределите методы, которые вы хотите использовать в Location3D, и измените тип возвращаемого значения:

public class Location {
    private int x;
    private int y;

    public Location setY(int y){
        this.y = y;
        return this;
    }

    public Location setX(int x){
        this.x = x;
        return this;
    }
}

И Location3D:

public class Location3D extends Location {

    private int z;

    public Location3D setY(int y){
        super.setY(y);
        return this;
    }

    public Location3D setX(int x){
        super.setX(x);
        return this;
    }

    public Location3D setZ(int z){
        this.z = z;
        return this;
    }
}

Подход к композиции:

public class Location {
    private int x;
    private int y;

    public Location setY(int y){
        this.y = y;
        return this;
    }

    public Location setX(int x){
        this.x = x;
        return this;
    }
}

И Location3D:

public class Location3D {

    private Location location = new Location();
    private int z;

    public Location3D setY(int y){
        location.setY(y);
        return this;
    }

    public Location3D setX(int x){
        location.setX(x);
        return this;
    }

    public Location3D setZ(int z){
        this.z = z;
        return this;
    }
}