Home > database >  How to return an instance of Inherited class from parent abstract class
How to return an instance of Inherited class from parent abstract class

Time:10-06

I'd like to solve such problem. I have some abstract class and a concrete class with setters that return the instance of that class:

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

next abstract:

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

and finally a concrete class:

@Entity
public class Person extends NamedEntity {
  private String address;

  public String getAddress() {
    return address;
  }

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

I'd like to use this kind of builder but in current setup it's not working due to different return types of parent setters

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

of course ther's a workaround with overriden setters but.... can it be done other way using generics?

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

CodePudding user response:

You can use the same trick as enums (Enum), a generic type parameter for the child class.

@MappedSuperclass
public abstract class BaseEntity<E extends BaseEntity<E>> implements Serializable {

  private Integer id;
  
  public Integer getId() {
    return id;
  }

  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 E setName(String name) {
    this.name = name;
    return this;
  }
}

For child classes of Person you need not continue with this pattern.

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

Now you can do_

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

The alternative is a Builder pattern, however it has the same inheritance problem, and you might end up with *.Builder classes inheriting from parent Builder classes.

I would even say it is not worth this boiler plate code, just for a fluent API (chained calls). The criteria API for instance does hardly need using created objects, and the passed values for the setters must come from some code too.

Also setters implies the classes are mutable. It would be much nicer if most fields were immutable. With entity classes unrealistic, but setters are an ugly initialisation. When possible use constructors/builders without setters.

CodePudding user response:

You can implement the Builder pattern by introducing a nested class Builder with a set of self-returning methods (i.e. returning an instance of Builder) which can be chained in a fluent way.

Method Builder.build() should return an instance of Person.

Note that you setters of your entities can be void.

That's how implementation might look like:

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

Usage example:

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

Note:

  • There could be multiple ways to obtain an instance of Builder. You can introduce in the Person class a static method builder() returning a new Builder, or static methods like withName(String), withId(Integer) might also be handy (for inspiration have a look at User class from Spring Security).
  • When dialing with immutable objects, Builder class should have all the field of the target class duplicated instead of keeping the reference to the target object. And in such case, method build() would be responsible for constructing an instance of the target type.

CodePudding user response:

Thanks for all the sugestions I know the builder pattern, but in this particular case is the same workaround as overriding the methods setId and setName The point here is: it is possible that setId method will return the instance of child class the method is called from

let's say I'd like to put a complex object to my builder (why not?):

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

as you may guess the person.setId returns instance of BaseEntity

  • Related