【问题标题】:How to return an instance of Inherited class from parent abstract class如何从父抽象类返回继承类的实例
【发布时间】:2022-10-21 15:21:09
【问题描述】:

我想解决这样的问题。我有一些抽象类和一个带有返回该类实例的设置器的具体类:

@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;
  }

当然有覆盖设置器的解决方法,但是....可以使用泛型以其他方式完成吗?

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

【问题讨论】:

  • 您的构建类位于哪里?是BaseEntity的成员吗?建设者不必流利。看起来你只是在构建一个 Person 然后设置它的属性。 person.setId(1);person.setName("name"); 等有什么问题?

标签: java generics inheritance


【解决方案1】:

感谢所有的建议 我知道构建器模式,但在这种特殊情况下,解决方法与覆盖方法 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 的实例

【讨论】:

    【解决方案2】:

    错误的答案

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

    @MappedSuperclass
    public abstract class BaseEntity<E extends BaseEntity<E>> implements Serializable {
    
      private Integer id;
      
      public Integer getId() {
        return id;
      }
    

    public BaseEntity&lt;E&gt; ...

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

    public NamedEntity&lt;E&gt; ...

      public E setName(String name) {
        this.name = name;
        return this;
      }
    }
    

    对于 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 类继承。

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

    setter 也意味着类是可变的.如果大多数字段都是不可变的,那就更好了。使用实体类是不切实际的,但是 setter 是一个丑陋的初始化。尽可能使用没有设置器的构造器/构建器。

    【讨论】:

    • 这在 Java SE 11 中无法编译。您尝试过吗?
    【解决方案3】:

    您可以通过引入嵌套类Builder 来实现Builder pattern,该类具有一组可以以流畅方式链接的自返回方法(即返回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) 这样的静态方法也可能很方便(寻找灵感看看User来自 Spring Security 的类)。
    • 使用不可变对象拨号时,Builder 类应该复制目标类的所有字段,而不是保留对目标对象的引用。在这种情况下,方法build() 将负责构造目标类型的实例。

    【讨论】:

    • 希望这得到赞成/接受。可惜现在有了新的提问者......
    猜你喜欢
    • 2018-07-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多