0

I like the builder pattern (example https://stackoverflow.com/a/1953567) for various reasons, e. g. the fact that I can work with immutable objects and that I can control the object creation in the way that no invalid objects can be created.

However, I try to follow the paradigm "program to interfaces, not implementations" (example https://stackoverflow.com/a/2697810).

I figured, these two guidelines do not play well together.

If I have an interface Person and a class PersonImpl and a builder PersonImplBuilder that builds a PersonImpl. I now can assure that every instance of PersonImpl is valid and immutable. But every return value and particularly every method parameter in my API uses the interface. So I can not depend on a valid object.

Am I missing something respectively is there another way of combining these two very useful guidelines?

EDIT

Some code to clarify. In this example the builder is useless in terms of ensuring validity and/or imutability of the object in my API. It does only guarantee that any object of PersonImpl is valid (and by the way that only works because PersonImpl ist declared as final). But I can not control if a client is actually using my safely constructed PersonImpl object or any other implementation of the Person interface.

public interface Person {
    LocalDate getBirthday();
}
public final class PersonImpl implements Person {
    private final LocalDate birthday;

    private PersonImpl(PersonImplBuilder builder) {
        this.birthday = builder.birthday;
    }

    @Override
    public LocalDate getBirthday() {
        return birthday;
    }
}
public class PersonImplBuilder {
    private LocalDate birthday;

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;
    }

    public PersonImpl build() {
        if(birthday.isAfter(LocalDate.now().minusYears(21).minusDays(1))) {
            throw new IllegalStateException("Person must be 21 years or above");
        }
        return new PersonImpl(this);
    }
}
// this is my API
public interface PersonService { 
    void doSomeAdultStuff(Person person);
}
public class PersonServiceImpl implements PersonService {
    //...
}
public void maliciousMethod() {
    PersonService service = new PersonServiceImpl();
    service.doSomeAdultStuff(new Person() {
        @Override
        public LocalDate getBirthday() {
            return LocalDate.now();
        }
    });
}
Filou
  • 490
  • 4
  • 17
  • Can you add some code? It's not clear which methods return the interface and why that's an issue. "Program to interfaces not implementations" does not require you to define a separate interface for every class since classes already expose an interface. – Lee May 24 '18 at 15:56

2 Answers2

0

You don't use the builder anywhere in your code. If you don't want to your code to use different implementations of the Person class but just your PersonImpl then don't use the interface but the concrete implementation. This way you will be sure that you have only objects build the way you want.

You should consider that a person can be less than 21 years old and still be a "valid" person (A child for example). You can have adult builder and child builder (different implementations) but you still would need to check if you got the right implementation. So maybe you should check in the service if the person has a correct age and not during building the object. Otherwise it should be called Adult and not a Person ;)

Veselin Davidov
  • 7,031
  • 1
  • 15
  • 23
  • Thanks for reply. You somehow outlined my statement. If you want to use benefits (validity, immutability) of builder pattern (your quote) "_then don't use the interface_" which means you can not follow paradigm “program to interfaces, not implementations”. As for your 2nd paragraph, the person is just an example. I only needed any class with any constraint. Imagine a use case where only adult persons do interact... – Filou May 28 '18 at 11:12
0

The combination of "program to interface" concept and Builder pattern should have no issue. The reason is in your following code:

service.doSomeAdultStuff(new Person() {
    @Override
    public LocalDate getBirthday() {
        return LocalDate.now();
    }
});

You wrote a new class that has no name and implements the Person interface (anonymous class). This class is different with PersonImpl class of your code. In your case just remove anonymous class implementation and use new PersonImpl(builder) instead.

service.doSomeAdultStuff(new PersonImpl(builder));
Quang Vien
  • 308
  • 2
  • 10
  • The `maliciousMethod()` is written by some client which I can not control. Although I provide an implementation of `Person` interface in form of my `PersonImpl` I do not know if a client uses it or if the client implements an own implementation or uses an anonymous class like in the example. btw.: Do note, that your last line of code is illegal because the constructor of `PersonImpl` ist private. To create object `PersonImpl` only use `new PersonImplBuilder().build()`. If you want to make constructor public yout need to make class `PersonImplBuilder` final too so no one can subclass it. – Filou May 28 '18 at 11:29
  • Sorry I didn't notice the contructor of PersonImpl is private. – Quang Vien May 28 '18 at 12:07