javafactory-patternabstract-factory

Right pattern for creating concrete classes of an interface


I have an interface which is implemented by different classes. I want to make the implementation classes package private so that clients only use the interface, but of course they somehow need to create new instances. I was reading about factory patterns and at first thought Abstract Factory could be what I need, but I'm struggling with the different constructor parameters of the implementation classes.

Example:

public interface Foo {

  void doSomething();

}

class FooImplA implements Foo {
  String prop1;
  Integer prop2;

  public FooImplA(String prop1,
                  Integer prop2) {
    this.prop1 = prop1;
    this.prop2 = prop2;
  }

  @Override
  public void doSomething() {

  }
}

class FooImplB implements Foo {
  String prop1;

  public FooImplB(String prop1) {
    this.prop1 = prop1;
  }

  @Override
  public void doSomething() {

  }
}

My solution now would be something like this:

public class FooFactory {

  public Foo createFooA(String prop1, Integer prop2) {
    return new FooImplA(prop1, prop2);
  }

  public Foo createFooB(String prop1) {
    return new FooImplB(prop1);
  }

}

Is this the "right" way to do it or are there better alternatives?


Solution

  • Your question isn't entirely clear, in that there are 2 completely different situations that can both be plausibly turned into this question. I'll answer both.

    The intent is that other projects can extend this system

    Think about java.util.List. Yes, this project (The java.util package as a project) ships with its own implementations of this interface, including ArrayList and LinkedList. However, that interface isn't designed to be limited: If you want to write your own list implementation in your own project you are free to do so, and List is designed to allow you to do this. Loads of projects use this: guava has ImmutableList, the java core libs itself has plenty of list implementations for example in the java.util.concurrent package, and so on.

    if this describes your situation, then the answer is:

    No, do not make that factory. Just make your implementations public. Yes, this means someone could write:

    FooImplA foo = new FooImplA();
    

    and you don't want them to, but I will free you from such concerns by providing you with this pithy but highly effective zen koan: "You can't stop an idiotfrom writing idiotic code; the universe is vastly smarter than you and will create a better idiot than you can write defenses to stop them".

    So, it is a pointless exercise, and this is freeing: There is no need to try. Make clear from the API, documentation, and examples how you're meant to do it. If someone is going to ignore all that and 'do it wrong', then let them.

    You'll then be setting the system up precisely the same way j.u.List is: You can write this today:

    ArrayList<String> list = new ArrayList<String>();
    

    It compiles just fine. One could say you should 'program to the interface' but as far as style problems go, this rates very lowly indeed. Mostly this doesn't matter. It's the final nit one would pick in a code review if truly nothing of any substance whatsoever is left to be said.

    The intent is that you created all the implementations; the system isn't designed to be extended and you don't want anybody to try it.

    In that case, you need to add a bunch of new language features to do this right:

    Sealed

    Java20 adds the notion of 'sealed interfaces'. A sealed interface (or class) lists every type that is allowed to implement/extend it, and no other types can do it. The aim is for you to list your own implementations and leave it at that. This means you can now enforce that nobody makes custom implementations.

    static methods in interfaces

    The problem with a 'factory' the way you have it, is that it hurts discoverability. I know about your interface type and I have a need to create an instance of it. Where do I look? Naturally, first I look in that interface, but I find no methods that can make anything. Next, I ask my IDE to show me a list of all types that implement it. My IDE gives me a bunch of private classes hidden deep inside your project that clearly I'm not meant to directly new Impl(), so now I'm stuck. I need to read docs (the horror. I kid, but only a little - if the average user of your library just intuits what to do based on nothing more than the names of your types and methods, that's far better than a system that confuses the heck out of me until I read the docs). Then maybe I see how to do it.

    In modern java you can shove static methods in interfaces. So, do that:

    // Foo.java:
    public sealed interface Foo permits FooImplA, FooImplB {
      void doSomething();
    
      public static Foo newImplA() {
        return new FooImplA();
      }
    
      public static Foo newImplB() {
        return new FooImplB();
      }
    }
    
    // FooImplA.java:
    class FooImplA implements Foo {
     // note how this is a package private class!
     ...
    
      FooImplA() { ... } // with a package private constructor!
    }
    

    This solves all problems: