1

With the factory pattern we abstract the creation of objects. But what if we need a specific configuration of an object that depends on the calling context?
Example:

So I have a Builder pattern for creating some types of objects that share the same interface
E.g.

private EmployeeBuilder createEmployee(EmployeeDataSource e) {  
    if(e instanceof Engineer) {
       return new EngineerEmployeeBuilder((EngineerDataSource) e);  
    }  
    else if(e instanceof Manager) {  
       return new ManagerBuilder((ManagerDataSource) e);  
    }  
    else if(e instanceof Salesman) {
       return new SalesmanBuilder((SalesmanDataSource) e);  
    }  
    // etc  
}

So far so good as the calling code can just do something like:

Manager m = createEmployee(ManagerDataSource ds).build();

The problem I have is that it turns out that I need to make some changes to the builder object but I can not do them inside the createEmployee as that is called from places that the change should not happen. E.g. the above snippet could become:

EmployeeBuilder eb = createEmployee(EmployeeDataSource ds);  
if(eb instanceof ManagerBuilder) {
    ((ManagerBuilder)eb).setRoleInc(INC_FOR_ROLE);  
}  
Employee e = eb.build();   
// use only Employee here

This looks like a code smell and would like to avoid it.
Is there a combination of design patterns I could use to enhance the original approach?

1

Just add some additional parameters (maybe optional ones) to createEmployee, even if those parameters or certain values of them only apply to certain builder types. That gives you the possibility of calling setRoleInc inside createEmployee when it is required, something along the lines of

private EmployeeBuilder createEmployee(EmployeeDataSource e, Role role=ROLE_NONE) {  
    if(e instanceof Engineer) {
       return new EngineerEmployeeBuilder((EngineerDataSource) e);  
    }  
    else if(e instanceof Manager) {  
       var mb = new ManagerBuilder((ManagerDataSource) e);  
       if(role !=ROLE_NONE)
           mb.SetRoleInc(role);
       return mb;
    }  
    else if(e instanceof Salesman) {
       return new SalesmanBuilder((SalesmanDataSource) e);  
    }  
    // etc  
}

(For the sake of demonstration, I used C# syntax for optional parameters, which is AFAIK not available/different in Java, but I guess you can transfer the solution to Java by yourself).

6
  • So what if the parameters should be different per type of object? E.g. Role role would be applicable only to Manager. Or I could have X number after that. How could I generalize this? – Jim Sep 7 '19 at 22:06
  • @Jim: by introducing a parameter class which can have different subclasses, for example a base class "Configuration", together with a ManagerConfiguration, SalesmanConfiguration etc. Or by simply providing different overloads of createEmployee with different parameters. – Doc Brown Sep 7 '19 at 22:08
  • But this assumes that the calling code knows what type EmployeeDataSource is so as to pass the concrete Configuration right? But then that defeats the purpose of the factory method right? I mean if the calling code was to "know" that the EmployeeDataSource is something that would construct a Manager so as to pass a ManagerConfiguration it could just create it on the spot instead of having the factory method. Am I misunderstanding something? – Jim Sep 7 '19 at 22:13
  • @Jim: for this, it would probably be the best to have a general "Configuration" object which is filled with all potential parameters from the environment which may be useful for those builders, at least one. – Doc Brown Sep 7 '19 at 23:37
  • How would that be best defined? Would the API for Configuration cover all possible options for each concrete EmployeeBuilder? – Jim Sep 8 '19 at 8:57
0

It looks like you have a repeated typo here. I assume that

if (e instanceof Engineer)

should really read

if (e instanceof EngineerDataSource)

(same story here with Manager and Salesman). Leave a comment if I'm wrong about that.

My actual problem with what you have here is that your createEmployee method is named incorrectly--it doesn't create an Employee. Rather, it creates an Employee*Builder*.

So I'd rename createEmployee to createEmployeeBuilder and then create a new method (this one called createEmployee) that looks like

private Employee createEmployee(EmployeeDataSource eds) {
  EmployeeBuilder eb = createEmployeeBuilder(eds);
  if (eb instanceof ManagerBuilder) {
    //do special configuration here
  }
  return eb.build();
}

You might have to add a parameter or two to this createEmployee method, depending on exactly what your special configuration is.

Alternatively, if you can get away with it, just stick that special configuration (in your example, setRoleInc(INC_FOR_ROLE)) inside the build() method for ManagerBuilder. Then you don't need that new createEmployee method at all and can just do:

Employee e = createEmployeeBuilder(eds).build();

(casting if necessary after that point).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.