3

Can somebody help a novice programmer to understand if his solution is correct?

My question is similar to the the following two:

What's wrong with overridable method calls in constructors?

Factory pattern in C#: How to ensure an object instance can only be created by a factory class?

Problem: I want to have subclasses which will differ only in their initialisation method. However, I also want to prevent instantiating these classes without initialization. In other words, I want to ensure, that some "initialize()" method will always be called after instantiation of a subclass:

public abstract class Data {

 protected Parameter dataSource;     

  Data(parameter1){
     this.dataSource = parameter1;

  loadData(); // should be called to initialise class fields and ensure correct work of other class methods
  }

 protected abstract loadData(){
     ... //uses dataSource
 }
}

So I decided to perform initialization on a constructor. It worked (now I know that it's a very bad practice) until I created a subclass where the initialize method used some additional parameters:

public class DataFromSpecificSources extends Data {

 private Parameter dataSource2;

 public DataFromSpecificSources(parameter1, parameter2){
    this.dataSource2 = parameter2; // I can't put it here because the constructor is not called yet
    super(parameter1); // this, of course, will not work
  }

 @Override
 private void loadData(){
   ... // uses both dataSource 1 and 2
       // or just dataSource2
  }
}

This, of course, is not going to work. And I started searching for a right pattern... After I read the answers on questions posted before, I decided to use the factory and limit visibility of the subclass constructor to the package:

My solution:

// factory ensures that loadData() method will be called
public class MyDataFactory(){

 public Data createSubClass(parameter1,parameter2){
  Data subClass;

  if (parameter2 != null){
   subClass = new DataFromSpecificSources(parameter1, parameter2);
   subClass.loadData();
  } else {
   subClass = new AnotherSubClass(parameter1);
   subClass.loadData()
  }

  return subClass;
 }

}


 public abstract class Data {

 protected Parameter dataSource;     

  Data(parameter1){
     this.dataSource = parameter1;
  }

 // I don't call it in constructor anymore - instead it's controlled within the factory
 protected abstract loadData(){
     ... //uses dataSource
 }
}



public class DataFromSpecificSources {

 private Parameter dataSource2;

 protected DataFromSpecificSources(){}

 // now this constructor is only visible within package (only for the factory in the same package)
 DataFromSpecificSources(parameter1, parameter2){
    super(parameter1); // it does not initialise data anymore

    this.dataSource2 = parameter2;
  }

  @Override
  protected void loadData(){
   ... // uses dataSources 1 and 2
  }
}

Now factory ensures that subclasses will be initialized (data will be loaded) and instantiation of subclasses is not allowed in other packages. Other classes have no access to constructor of subclasses and are forced to use factory to get an instance of a subclass.

I just wanted to ask if my solution is correct (logically) and Factory method with subclass constructor visibility limited to the package is right choice here?! Or there is any other more effective pattern solving the problem?!

Community
  • 1
  • 1
Andrey Sapegin
  • 454
  • 8
  • 33
  • Elegent solution. However is there a need for the Factory to be its own class? Could the factory method be a static method in `Data`? – John B Sep 14 '12 at 15:10
  • @JohnB Since his choice of implementation depends on a parameter, I'd say no, a factory class is a much better choice. – Brian Sep 14 '12 at 15:18

1 Answers1

3

Using a factory is definitely a step in the right direction. The issue I see is that what happens when you want to add a third class that takes a third parameter. Now your Factory is either going to have to have a second overloaded createSubClass method taking the third parameter, or all your code is going to have to be rewritten to provide the third parameter. Additionally you are forcing anyone using the Factory to specify null for the second parameter even if they only want the single parameter class.... when you get to the class that takes 15 parameters how are you going to remember which parameter is which

The solution to this is to use the Builder pattern instead.

public class MyDataBuilder(){
    private parameter1 = null;
    private parameter2 = null;

    public MyDataBuilder withParameter1(parameter1) {
        this.parameter1 = parameter1;
        return this;
    }

    public MyDataBuilder withParameter2(parameter2) {
        this.parameter2 = parameter2;
        return this;
    }

    public Data createSubClass(){
        Data subClass;

        if (parameter2 != null){
            subClass = new DataFromSpecificSources(parameter1, parameter2);
        } else {
            subClass = new AnotherSubClass(parameter1);
        }
        subClass.loadData();
        return subClass;
    }

}

Now the code creating the Data instances can work like so:

Data data = new MyDataBuilder().withParameter1(param1).withParameter2(param2).create();

or

Data data = new MyDataBuilder().withParameter1(param1).create();

And that code is future-proofed for when you add parameter3... and you can even have the builder with a non-null default for parameter3 if you so need that.

The next thing you notice is that you now have this nice Builder object that contains all the required parameters... so now you can add getters to the Builder and just pass the Builder as the constructor parameter, e.g.

public class DataFromSpecificSources {
   ...

   DataFromSpecificSources(MyDataBuilder builder){
       ...
   }

   ...

}

So that you now almost have a standard constructor signature

Now for some Java specific improvements. We can make the builder not need to know about the sub-classes at all!

Using a DI framework we can inject the classes that implement the Data interface / abstract class into the Builder and then just iterate through each class until we find a class that supports the configuration of the Builder instance.

The poor-man's DI framework is the /META-INF/services contract and the ServiceLoader class available since JRE 1.6 (though the core logic has been in Java since 1.2)

Your builder's create method will then look a little something like

public Data create() {
    for (DataFactory factory: ServiceLoader.load(DataFactory.class)) {
        if (factory.canCreate(this)) {
           Data result = factory.newInstance(this);
           result.loadData();
           return result;
        }
    }
    throw new IllegalStateException("not even the default instance supports this config");
}

Whether you want to go to that extreme is questionable... but since you might come across it at some point in time when looking at other people's code, it is probably a good time to point it out to you now.

Oh, the reason why we have to add a Factory class to be looked up by the ServiceLoader is because ServiceLoader expects to call the default constructor, and we have hidden the default constructor so we use a Factory class to do the work for us and allow us to keep the constructor hidden.

There is nothing preventing the Factory classes from being static inner classes in the Data classes (which gives them great visibility on the class they are creating), e.g.

public class UberData extends Data {
    private UberData(MyDataBuilder config) {
        ...
    }

    public static class Factory extends DataFactory {
        protected Data create(MyDataBuilder config) {
            return new UberData(config); 
        }
        protected boolean canCreate(MyDataBuilder config) {
            return config.hasFlanges() and config.getWidgetCount() < 7;
        }
    }
}

As we can then list in META-INF/services/com.mypackage.DataFactory

com.mypackage.UberData.Factory
com.mypackage.DataFromSpecificSources.Factory
com.some.otherpackage.AnotherSubClass.Factory

The best bit about this type of solution is it allows adding additional implementations just by adding those implementations to the classpath at run-time... i.e. very loose coupling

Stephen Connolly
  • 13,872
  • 6
  • 41
  • 63