0

This is a follow up of my earlier question.
I want to create a factory like approach to be able to call different methods depending on the type.
The following code works (note it is long but it is a very simplified version to show the structure. In reality for instance there won't be just one Person added to the result list):

public class TestBuilder <T>{
    private interface Processor <T> {
        List<Person> process(T t, String firstName, String lastName, String id, String cc);
    }

    private class FooProcessor implements Processor <Foo> {
        @Override
        public List<Person> process(Foo foo, String firstName, String lastName, String id, String cc) {
            System.out.println("processFoo");
            Person p = new Person(firstName, lastName);
            p.setId(foo.getId());;
            return new ArrayList<>(Arrays.asList(p));
        }
    }

    private class BarProcessor implements Processor <Bar> {
        @Override
        public List<Person> process(Bar bar, String firstName, String lastName, String id, String cc) {
            System.out.println("processBar");
            Person p = new Person(firstName, lastName);
            p.setCc(bar.getCC());
            return new ArrayList<>(Arrays.asList(p));
        }
    }

    private Map<AnInterface<T>, Processor <T>> map = new HashMap<>();
    private TestBuilder() {
        map.put((AnInterface<T>) new Foo(), (Processor<T>) new FooProcessor());
        map.put((AnInterface<T>) new Bar(), (Processor<T>) new BarProcessor());
    }


    public List<Person> process(T t, String firstName, String lastName, String id, String cc) {
        return map.get(t).process(t, firstName, lastName, id, cc);
    }


    public static void main(String[] args) {
        List<Person> personList = new TestBuilder().process(new Foo(), "John", "Doe", "123", null);
        System.out.println(personList);
    }
}

The issues I have that I'd like help with are the following:

I am passing null in some cases in the process method because not all arguments are relevant for all types. Hence I also want to ask how can I make the approach more flexible i.e. so that I am able (as I add a new type) to pass if possible only the relevant arguments without making the code complicated?

Jim
  • 3,845
  • 3
  • 22
  • 47

1 Answers1

2

I suggest to adopt the builder pattern to build a `Person. This pattern allows you to specify optional args and has the added value of "naming" args through fluent setter methods. something like

private interface Processor <T> {
    public static class Builder {
        private T t = null;  // assign default values
        private String firstName = null;
        private String lastName = null;
        private String cc = null;

        public Builder with(T t) {
            this.t = t;
            return this;
        }
        public Builder withFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }
        // same fluent setters for all instance vars
        public Processor build() {
            return new Processor(...);
        }
    }

    List<Person> process(T t, String firstName, String lastName, String id, String cc);
}

Processor processor = Processor.Builder
    .with(new Foo())
    .withFirstName("John")
    .withLastName("Doe")
    // no explicit set for cc, so default is used
    .build();
processor.process();

Update Oct 24th

following the comments, I went and re-examined the sample code from the question and realised that the builder pattern can apply to build an instance of Person and that instance can be passed to the process() method:

public class PersonBuilder {
    private Person person = null;

    public PersonBuilder(String firstName, String lastName) {
        person = new Person(firstName, lastName);
    }
    public PersonBuilder withId(String id) {
        person.setId(id);
        return this;
    }
    public PersonBuilder withCc(String cc) {
        person.setCc(cc);
        return this;
    }
    public Person build() {
        return person;
    }
}

the definition of process() is simplified to

private interface Processor <T> {
    List<Person> process(T t, Person person);
}

and the call in main() becomes

public static void main(String[] args) {
    List<Person> personList = new TestBuilder().process(new Foo(),
        // no null arg
        new PersonBuilder("John", "Doe").withId("123").build());
    System.out.println(personList);
}
Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47
  • Would `map` then belong to the builder? I am not sure how would `build()` work – Jim Oct 23 '22 at 19:24
  • How would I be able to get `FooProcessor` vs `BarProcessor` using the approach you mention? It is not clear to me. Could you please elaborate? – Jim Oct 23 '22 at 19:27
  • The approach I think misses the point which is to pick the proper implementation based on the specific type `T`. – Jim Oct 23 '22 at 19:42
  • Yes, because depending on the passed T you would either use a FooBuilder or a BarBuilder. – Valerij Dobler Oct 23 '22 at 20:09
  • @ValerijDobler: So where exactly would I make the distinction? Would I use `map` inside `build()`? – Jim Oct 23 '22 at 20:21
  • No, you would have to call in place either `Foo.with().build()` or `Bar.with().build()`. – Valerij Dobler Oct 24 '22 at 06:45
  • The Factory Pattern is for constructing something bigger, which consists of interchangeable parts. So, if you would have a Baz which would consist of a IFooable and both Foo and Bar would implement IFooable, then you could start thinking about using a BazFacory. But even in this example, a BazBuilder with a `fooable` buildstep would also suffice. – Valerij Dobler Oct 24 '22 at 06:48
  • see edited answer. builder pattern should build instance of `Person`. you can use factory or map to manage processor instantiation – Sharon Ben Asher Oct 24 '22 at 07:18
  • Where should the `List process(T t, Person person);` go then? In the builder or on its own in a separate file? – Jim Oct 24 '22 at 07:40
  • I can not update `Person` actually. So this can not work for me – Jim Oct 24 '22 at 07:51
  • what do you mean can not update `Person`. code in question is calling `p.setId(id); `. in fact, the builder is only calling same exact methods – Sharon Ben Asher Oct 24 '22 at 08:04
  • First of all, I am returning a `List`. Not a `Person`. And I use the concrete `T` to determine how I populate the list. In your example you just update the `Person` but this is not related to what I am doing. I am really confused by your answer to be honest. I really don't understand how I can use it. – Jim Oct 24 '22 at 08:07
  • By can not update the `Person` I mean changing the source code for `Person` – Jim Oct 24 '22 at 08:10
  • `Builder` can, of course, be separate class in separate file – Sharon Ben Asher Oct 24 '22 at 08:14
  • 1
    see edited answer. my solution is for (originally fourth question) of building person with optional properties. you no longer need to specify null args – Sharon Ben Asher Oct 24 '22 at 08:20
  • The issue is that I need to create multiple `Person` and not one. And I need to use `Foo|Bar` along with the params to be able to do that – Jim Oct 24 '22 at 08:42
  • the code and text in question do not reflect that. you can create multiple persons with builder – Sharon Ben Asher Oct 24 '22 at 08:44
  • look, builder pattern solves the _general_ problem of initializing java object with multiple, optional properties without multiple constructors or null args. seems to me like what you need is to read up on the builder pattern and try to adopt the pattern to your specific needs – Sharon Ben Asher Oct 24 '22 at 08:48
  • I understand that, but I am not sure how it connects with my `process` method. Unless I create a builder for the arguments to create an `Argument` object and pass that some `Argument` object in the concrete process which then picks the members it needs. Is this similar to what you are saying? – Jim Oct 24 '22 at 08:52
  • quote from the post: "I am passing null in some cases in the process method because not all arguments are relevant for all types". your `process()` method receives optional args that are properties of one `Person`. the builder pattern solves the issue of those optional args. you still need to pass Foo or Bar instances but with the builder, there are no more null values passed to `process()`. isn't that what you wanted to solve? – Sharon Ben Asher Oct 24 '22 at 12:41
  • 2
    “*The issue is that I need to create multiple Person and not one*” the code posted in your question always creates a list containing exactly one element and it wouldn’t make much sense to produce more than one object with the same arguments. Code for producing multiple elements would have to look completely different. So you have oversimplified your example. – Holger Oct 24 '22 at 13:40
  • @Holger: Fair point, I messed up trying to make it simple for convenience of readers – Jim Oct 26 '22 at 19:02