如何从父抽象类返回Inherited类的实例



我想解决这样的问题。我有一些抽象类和一个带有setter的具体类,它们返回该类的实例:

@MappedSuperclass
public abstract class BaseEntity implements Serializable {
private Integer id;

public Integer getId() {
return id;
}
public BaseEntity setId(Integer id) {
this.id = id;
return this;
}
}

下一篇摘要:

@MappedSuperclass
public abstract class NamedEntity extends BaseEntity {
private String name;
public String getName() {
return name;
}
public NamedEntity setName(String name) {
this.name = name;
return this;
}
}

最后是一个具体的类别:

@Entity
public class Person extends NamedEntity {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}

我想使用这种构建器,但在当前的设置中,由于父设置的返回类型不同,它不起作用

public Person build() {
Person person = new Person()
.setId(1);          //return BaseEntity instead of Person
.setName("name")    //returns NamedEntity instead of Person
.setAddress("foo"); //return Person!
return person;
}

当然,有一个替代setter的变通方法,但是。。。。可以使用泛型以其他方式完成吗?

@Override
public Person setId(Integer id) {
super.setId(id);
return this;
}

@Override
public Person setName(String name) {
super.setName(name);
return this;
}

感谢您的建议我知道构建器模式,但在这种特殊情况下,解决方法与重写方法setIdsetName相同重点是:setId方法可能会返回从调用该方法的子类的实例

比方说,我想把一个复杂的对象放在我的构建器中(为什么不呢?(:

public class Person extends NamedEntity {
private String address;
... getters/setters

public Builder builder() {
return new Builder();
}

public final static class Builder {
private final Person person;
private Long id;
private String name;
private String address;

private Builder() { 
this.person = new Person(); 
}

public Builder withId(Long id) {
person.setId(id);
return this;
}
..... other setters

public Builder withDto(PersonDTO dto) {
person
.setId(dto.getId())
.setName(dto.getName())
.setAddress(dto.getAddress()
}

public Person build() {
return person;
}
}
}

正如您可能猜测的那样,person.setId返回BaseEntity 的实例

您可以使用与enums(Enum(相同的技巧,enums是子类的泛型类型参数。

@MappedSuperclass
public abstract class BaseEntity<E extends BaseEntity<E>> implements Serializable {
private Integer id;

public Integer getId() {
return id;
}
protected final E getThis() {
return this;
}
public E setId(Integer id) {
this.id = id;
return getThis();
}
}
@MappedSuperclass
public abstract class NamedEntity<E extends NamedEntity<E>> extends BaseEntity<E> {
private String name;
public String getName() {
return name;
}
public E setName(String name) {
this.name = name;
return getThis();
}
}

对于Person的子类,您不需要继续使用此模式。

@Entity
public class Person extends NamedEntity<Person> {
private String address;
public String getAddress() {
return address;
}
public Person setAddress(String address) {
this.address = address;
return this;
}
}

现在你可以做_

Person einstein = new Person()
.setId(76)
.setName("Albert")
.setAddress("Princeton, New Jersey");

另一种选择是Builder模式,但它也有同样的继承问题,最终可能会出现从父Builder类继承的*.Builder类。

我甚至会说,仅仅为了一个流畅的API(链式调用(,这个锅炉板代码是不值得的。例如,标准API几乎不需要使用创建的对象,并且为setter传递的值也必须来自某些代码。

setters还暗示类是可变的。如果大多数字段都是不可变的,那就更好了。实体类不切实际,但setter是一个丑陋的初始化。尽可能使用不带setter的构造函数/构造函数。

您可以通过引入嵌套类Builder来实现Builder模式,该类具有一组自返回方法(即返回Builder的实例(,这些方法可以以流畅的方式链接。

方法Builder.build()应该返回一个Person的实例。

注意 您的实体的设置者可以是void

这就是实现的样子:

public class Person extends NamedEntity {
private String address;

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public static class Builder {
private Person person;
public Builder() {
this.person = new Person();
}

public Builder name(String name) {
person.setName(name);
return this;
}
public Builder address(String address) {
person.setAddress(address);
return this;
}
public Builder id(Integer id) {
person.setId(id);
return this;
}
public Person build() {
return person;
}
}
}

用法示例:

Person person = new Person.Builder()
.name("Alice")
.address("Wonderland")
.id(1)
.build();

注意:

  • 可能有多种方法可以获得Builder的实例。您可以在Person类中引入一个返回新Builder的静态方法builder(),或者像withName(String)withId(Integer)这样的静态方法也可能很方便(要获得灵感,请查看SpringSecurity中的User类(
  • 当使用不可变对象拨号时,Builder类应该复制目标类的所有字段,而不是保留对目标对象的引用。在这种情况下,方法build()将负责构建目标类型的实例

最新更新