Свободные сеттеры с наследованием в 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;
}
}