Search code examples
javaoopjava-8instantiationfactory-pattern

Consider Adding Static Interface Methods to Provide a Single Entry Point for Object Creation


Does "implementation" below mean instance of an object? Perhaps they mean the implementing code.

Consider adding static interface methods, to allow the client code to create (potentially specialized) objects that implement the interface. For example, if we have an interface Point with two methods int x() and int y(), then we can expose a static method Point.of(int x, int y) that produces a (hidden) implementation of the interface.

So, if x and y are both zero, we can return a special implementation class PointOrigoImpl (with no x or y fields), or else we return another class PointImpl that holds the given x and y values. Ensure that the implementation classes are in another package that are clearly not a part of the API (e.g. put the Point interface in com.company. product.shape and the implementations in com.company.product.internal.shape).

Do This:

Point point = Point.of(1,2);

Don't Do This:

Point point = new PointImpl(1,2);

The advantage being that this enforces encapsulation? However, if the implementation is package private and not part of the API, would that not create access problems? If the client, "world" as here:

https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

doesn't have access to the implementation of Point then how can it possibly instantiate a new Point as above for "do this"?


Solution

  • For example, if we have an interface Point with two methods int x() and int y(), then we can expose a static method Point.of(int x, int y) that produces a (hidden) implementation of the interface.

    1) Be aware, since Java 8 you can add a static method in an interface, but before you cannot.

    2) I have some difficulties to understand this advise :

    So, if x and y are both zero, we can return a special implementation class PointOrigoImpl (with no x or y fields), or else we return another class PointImpl that holds the given x and y values. Ensure that the implementation classes are in another package that are clearly not a part of the API (e.g. put the Point interface in com.company. product.shape and the implementations in com.company.product.internal.shape).

    Here is an example Point class that matches to the text you refer to:

    package com.company.product.shape;
    
    import com.company.product.internal.shape.PointOrigoImpl;
    import com.company.product.internal.shape.PointImpl;
    
    public interface Point{
       int x();
       int y();     
       static Point of(int x, int y) {
          if (x == 0 && y == 0){
             return new PointOrigoImpl();
          }
          return new PointImpl(x, y);
      }
    }
    

    If the Point interface is provided to clients and that it declares the factory method, to compile fine, the Pointinterface has to necessarily have the visibility to PointOrigoImpl and PointImpl classes in order to instantiate them.

    As the Point interface and the subclasses are in two distinct packages (com.company.product.shape and com.company.product.internal.shape), that means that PointImpl and PointOrigoImpl should have a public constructor otherwise Point could not instantiate them.
    Finally, clients who manipulate Points may also instantiate the constructor of PointImpl and PointOrigoImpl subclasses.

    It defeats the purpose of a factory that doesn't want to expose the implementation to choose itself the implementation to return.

    An interface is public and is designed to be fully accessible to the client.
    So as a general rule it should not contain implementations that we don't want to expose to clients.

    3) Here :

    The advantage being that this enforces encapsulation? However, if the implementation is package private and not part of the API, would that not create access problems? If the client, "world" as here: doesn't have access to the implementation of Point then how can it possibly instantiate a new Point as above for "do this"?

    I suppose you wonder why the text says : Ensure that the implementation classes are in another package that are clearly not a part of the API.

    In the text you refer to, the author wants to let the interface be known by Point clients but he doesn't want to subclasses of Point be known because he wants to decide the Point subclass to return according to the parameter used in the factory Point.

    Point clients will know only this interface.

    In Java, in order to not providing the access to implementation classes you can use public class, interface and inner private class :

    • the public class is exposed to clients to call the factory method that returns the implementation
    • the interface class is known by all and has no coupling with the implementation.
    • The inner private class makes part of the public class. The client cannot sees it but the factory see it to return the suitable implementation.

    With the example you refer to, it would be straight.

    Point (interface) :

    public interface Point{
       int x();
       int y();            
    }
    

    Point factory (public class that contains private classes) :

    public final class PointFactory {
    
        // private classes
        private class PointImpl implements Point {
    
            private int x;
            private int y;
    
            private PointImpl(int x, int y) {
                this.x = x;
                this.y = y;
            }
    
            public int x() {
                return x;
            }
    
            public int y() {
                return y;
            }
        }
    
        private class PointOrigoImpl implements Point {
    
            public int x() {
                return 0;
            }
    
            public int y() {
                return 0;
            }
        }
    
        // end private classes
    
        public Point of(int x, int y) {
            if (x == 0 && y == 0) {
                return new PointOrigoImpl();
            }
            return new PointImpl(x, y);
        }
    
    }