2
\$\begingroup\$

I am studying about design principles and design patterns. I have implemented an Abstract factory pattern as per my understanding. The scenario I have considered is as follows :

  1. There is an organization 'DrawingStandards.ltd' for setting standard rules for drawing.
  2. There are two Other organizations 'Shape.ltd' and 'EnhancedShape.ltd' which provides Shapes as per the rules set by the 'DrawingStandards.ltd'.
  3. There is another organization 'Graphics.ltd' which uses the Shapes implemented by Shape.ltd and 'EnhancedShape.ltd'.

Code from DrawingStandard.ltd

  1. Drawable.java

    package com.drawing.drawable;
    
    /**
     * Interface for DrawableFactory, implement this for any new Drawable shape.
     *
     * @author krishna.k@drawingstandards.com
     *
     */
    public interface Drawable {
        /**
         * draws the concrete drawable.
         */
        public void draw();
    }
    
  2. DrawableFactory.java

    package com.drawing.drawable.factory;
    
    import com.drawing.drawable.Drawable;
    
    /**
     * Abstract Factory to create the concrete drawables based on the input. Extend
     * this class to provide concrete implementation for creation of any new product
     *
     * @author krishna.k@drawingstandards.com
     *
     */
    public abstract class DrawableFactory {
        /**
         *
         * @param factory
         * @return concrete drawable object as created by the factory
         */
        public final Drawable getDrawable(DrawableFactory factory) {
            return factory.createDrawable();
        }
        /**
         *
         * @return Drawable created by the concrete factory.
         */
        protected abstract Drawable createDrawable();
    }
    

These classes have been released via jar.

Now Code from Shapes.ltd

  1. Circle.java

    package com.shape.drawables;
    
    import com.drawing.drawable.Drawable;
    
    /**
     * Drawable class representing a Circle.
     *
     * @author krishna.k@shape.com
     *
     */
    public class Circle implements Drawable {
        private int radius;
    
        /**
         * Takes radius of the circle as input
         *
         * @param radius
         */
        public Circle(int radius) {
            this.radius = radius;
        }
    
        /**
         * provides concrte implementation for draw method specific to circle. It
         * uses the length of the radius passed in the constructor to draw the
         * circle.
         */
        @Override
        public void draw() {
            System.out.println("Drawing the circle with radius = " + radius);
        }
    
    }
    
  2. Square.java

    package com.shape.drawables;
    
    import com.drawing.drawable.Drawable;
    
    /**
     * Drawable for drawing a square.
     *
     * @author krishna.k@shape.com
     *
     */
    public class Square implements Drawable {
        private int sideLength;
    
        /**
         * Takes the input as length of the side
         *
         * @param sideLength
         */
        public Square(int sideLength) {
            this.sideLength = sideLength;
        }
    
        /**
         * provides concrete implementation to the draw method, specific to square,
         * defined in Drawable. interface. It uses the length of the side passed in
         * the constructor to draw the square.
         */
        @Override
        public void draw() {
            System.out.println("Drawing a Square with side length =" + sideLength);
        }
    
    }
    
  3. Triangle.java

    package com.shape.drawables;
    
    import com.drawing.drawable.Drawable;
    
    /**
     * Drawable representing a Triangle
     *
     * @author krishna.k@shape.com
     *
     */
    public class Triangle implements Drawable {
        private int sideOneLength;
        private int sideTwoLength;
        private int sideThreeLength;
    
        /**
         * takes input for the three sides.
         *
         * @param sideOneLength
         * @param sideTwoLength
         * @param sideThreeLength
         */
        public Triangle(int sideOneLength, int sideTwoLength, int sideThreeLength) {
            this.sideOneLength = sideOneLength;
            this.sideTwoLength = sideTwoLength;
            this.sideThreeLength = sideThreeLength;
        }
    
        /**
         * provides concrete implementation to draw method specific to the circle.
         *
         * It uses the length of the three sides passed in the constructor to draw
         * the triangle.
         */
        @Override
        public void draw() {
            System.out.println("Drawing a Triangle with side one length = " + sideOneLength + " side two length = "
                    + sideTwoLength + " side three length = " + sideThreeLength);
        }
    }
    

Now the factory classes

  1. CircleFactory.java

    package com.shape.drawables.factories;
    
    import com.drawing.drawable.Drawable;
    import com.drawing.drawable.factory.DrawableFactory;
    import com.shape.drawables.Circle;
    
    /**
     * Concrete Factory for Cicle provided by Shape.ltd
     *
     * @author krishna.k@shape.com
     *
     */
    public class CircleFactory extends DrawableFactory {
        private int radius;
    
        /**
         * takes length radius as parameter
         *
         * @param radius
         */
        public CircleFactory(int radius) {
            this.radius = radius;
        }
    
        @Override
        protected Drawable createDrawable() {
            return new Circle(radius);
        }
    
    }
    
  2. SquareFactory.java

    package com.shape.drawables.factories;
    
    import com.drawing.drawable.Drawable;
    import com.drawing.drawable.factory.DrawableFactory;
    import com.shape.drawables.Square;
    
    /**
     * Concrete factory for Square.by Shape.ltd
     *
     * @author krishna.k@shape.com
     *
     */
    public class SquareFactory extends DrawableFactory {
        private int sideLength;
    
        /**
         * takes the length of the side of the square
         *
         * @param sideLength
         */
        public SquareFactory(int sideLength) {
            this.sideLength = sideLength;
        }
    
        @Override
        protected Drawable createDrawable() {
            return new Square(sideLength);
        }
    
    }
    
  3. TriangleFactory.java

    package com.shape.drawables.factories;
    
    import com.drawing.drawable.Drawable;
    import com.drawing.drawable.factory.DrawableFactory;
    import com.shape.drawables.Triangle;
    
    /**
     * Concrete implementation of Factory class for Triangle
     *
     * @author krishna.k@shape.com
     *
     */
    public class TriangleFactory extends DrawableFactory {
        private int sideOneLength;
        private int sideTwoLength;
        private int sideThreeLength;
    
        /**
         * Takes the length of three sides as parameter
         *
         * @param sideOneLength
         * @param sideTwoLength
         * @param sideThreeLength
         */
        public TriangleFactory(int sideOneLength, int sideTwoLength, int sideThreeLength) {
            this.sideOneLength = sideOneLength;
            this.sideTwoLength = sideTwoLength;
            this.sideThreeLength = sideThreeLength;
        }
    
        @Override
        protected Drawable createDrawable() {
            return new Triangle(sideOneLength, sideTwoLength, sideThreeLength);
        }
    }
    

The code from EnhancedShapes.ltd

Similar classes as provided by Shapes.ltd (Circle.java,Triangle.Java,Square.java and the corresponding factory classes.) under the package package com.enhancedshape.drawables and package com.enhancedshape.drawables.factories;

Now the code from 'Graphics.ltd' Using the shapes implemented by two vendors above.

  1. ShapePainter.java

    package com.graphichs;
    
    import com.drawing.drawable.Drawable;
    import com.drawing.drawable.factory.DrawableFactory;
    
    /**
     * Fetches the best factory as per the resources availability and provides the
     * shape
     *
     * @author krishna.k@graphics.com
     *
     */
    public class ShapePainter {
        private static long MIN_MEMORY_FOR_ENHANCED_SHAPE = 20796757;
    
        /**
         * Returns the best Square as per the resources available
         *
         * @param sideLength
         * @return Square
         */
        public Drawable getSquare(int sideLength) {
            DrawableFactory factory = null;
            if (Runtime.getRuntime().freeMemory() < MIN_MEMORY_FOR_ENHANCED_SHAPE) {
                factory = new com.shape.drawables.factories.SquareFactory(sideLength);
            } else {
                factory = new com.enhancedshape.drawables.factories.SquareFactory(sideLength);
            }
            return DrawableFactory.getDrawable(factory);
        }
    
        /**
         * Returns the best Circle as per the resources available
         *
         * @param radius
         * @return Circle
         */
        public Drawable getCircle(int radius) {
            DrawableFactory factory = null;
            if (Runtime.getRuntime().freeMemory() < MIN_MEMORY_FOR_ENHANCED_SHAPE) {
                factory = new com.shape.drawables.factories.CircleFactory(radius);
            } else {
                factory = new com.enhancedshape.drawables.factories.CircleFactory(radius);
            }
            return DrawableFactory.getDrawable(factory);
        }
    
        /**
         * Returns the best Triangle as per the resources available
         *
         * @param sideOneLength
         * @param sideTwoLength
         * @param sideThreeLength
         * @return Triangle
         */
        public Drawable getTriangle(int sideOneLength, int sideTwoLength, int sideThreeLength) {
            DrawableFactory factory = null;
            if (Runtime.getRuntime().freeMemory() < MIN_MEMORY_FOR_ENHANCED_SHAPE) {
                factory = new com.shape.drawables.factories.TriangleFactory(sideOneLength, sideTwoLength, sideThreeLength);
            } else {
                factory = new com.enhancedshape.drawables.factories.TriangleFactory(sideOneLength, sideTwoLength,
                        sideThreeLength);
            }
            return DrawableFactory.getDrawable(factory);
        }
    }
    
  2. DriverProgram.java

    package com.graphichs;
    
    import com.drawing.drawable.Drawable;
    
    public class DriverProgram {
        public static void main(String[] args) {
            ShapePainter shapePainter = new ShapePainter();
            Drawable circle = shapePainter.getCircle(20);
            Drawable square = shapePainter.getSquare(30);
            circle.draw();
            square.draw();
        }
    }
    

Please provide your valuable review feedback.

\$\endgroup\$
6
\$\begingroup\$

If it is your goal to learn about the abstract factory pattern, that is fine. In that case I would make two changes to DrawableFactory:

  • Get rid of the getDrawable method (was it meant to be static?). It doesn't provide any value. Also make createDrawable() public.

  • Create a type parameter for the type of Drawable created. You could then write type-safe code that calls an arbitrary DrawableFactory, does something with the Drawable, and returns it as its concrete type.

  • Make DrawableFactory an interface. In Java 8, interfaces with one method are called functional interfaces and can be constructed with lambdas and method references. Even if this functionality is advanced for you, others who use DrawableFactory might want to take advantage of it.

  • Make the fields of your factory implementations final. Then your factories are immutable and thus thread safe and less likely to have bugs down the road.

Then your code would look like:

public interface class DrawableFactory<D extends Drawable> {
    D createDrawable();
}

public class CircleFactory implements DrawableFactory<Circle> {
    private final int radius;
    public Circle createDrawable() {
        return new Circle(radius);
    }
}

etc.

But if your goal is to also learn when to use this pattern, I think this case is a good example of over-design. What value do CircleFactory, TriangleFactory, and SquareFactory provide? They seem to simply wrap the Circle, Triangle, and Square constructors. I would not create factories for that purpose - I would create the Drawables directly. If requirements did change in a way that warranted factories, I would then and only then refactor to introduce factories.

One more point: Runtime.getRuntime().freeMemory() is a very bad way to determine if there is enough memory to create an object. It tells you how much memory is free at the moment but doesn't account for how much memory is filled with garbage that could be freed if the space were needed and nor does it account for how much the heap size could grow if you use all the memory. The only reliable way I am aware of to determine if there is enough memory to create an object is to try to create it. Then you would catch OutOfMemoryError and return the smaller object in that case.


You asked for more feedback on the abstract factory pattern. I'll give you my two cents here.

What is the purpose of the factory pattern? It abstracts out the process of creating an object. It hides the exact implementation and can use information about the system or about parameters passed in to choose the implementation. It can have multiple lines of code hidden behind a single method call for convenience.

Abstract factories provide an additional level of abstraction, as now the implementation of the factory is also not set in stone. The abstract factory pattern provides a uniform API across multiple factory implementations and allows the code that instantiates the factory to choose the implementation and then pass the factory off to separate code that is agnostic to the implementation. (For example, this code might use the factory to create 10 instances and put them in a list.)

The abstract factory pattern thus provides two levels of abstraction: one over the factory implementation and the other over the implementation of the items that the factory creates. Abstraction is one of the most powerful tools in OOP. However, if used excessively, it can make your code roundabout and difficult to understand. Only use the abstract factory pattern when you actually need both levels of abstraction.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the feedback, For the factories classes, suppose there are some resources needed, some object of Some Canvas.java needed, to create the objects of Shape classes. In that way the factories would be hiding all those from the driver code (client code). Actually I wish to enhance my understanding of abstract factory pattern. Can you provide more feedback in abstract factory implementation point of view. \$\endgroup\$ – Krishna Kumar Mar 14 '16 at 12:02
  • \$\begingroup\$ I updated my answer with some general comments on the abstract factory pattern and with a point to make DrawableFactory an interface. \$\endgroup\$ – Solomonoff's Secret Mar 14 '16 at 12:48
  • \$\begingroup\$ Thanks for the nice points... I already up-voted your answer and feel like doing it again :). I have another question posted. I am not able to convince myself for the need of base abstract factory. stackoverflow.com/questions/35987934/… \$\endgroup\$ – Krishna Kumar Mar 14 '16 at 12:53
2
\$\begingroup\$

I think that you have missed a really great opportunity to use factory ;)

Look at your code:

    DrawableFactory factory = null;
    if (Runtime.getRuntime().freeMemory() < MIN_MEMORY_FOR_ENHANCED_SHAPE) {
        factory = new com.shape.drawables.factories.CircleFactory(radius);
    } else {
        factory = new com.enhancedshape.drawables.factories.CircleFactory(radius);
    }
    return DrawableFactory.getDrawable(factory);

I my opinion that is the place for factory: wrapping the decision of which package - shape or enchancedshape to use.

I'd write something like this:

    DrawableFactory factory = new FactoryOfFactories(Runtime.getRuntime().freeMemory());
    return DrawableFactory.getDrawable(factory);

And put all memory checking stuff in the FactoryOfFactories class.

The next step will be, as Solomonoff's Secret suggested, getting rid of your DrawableFactory. Then, FactoryOfFactories will be ready to be renamed... for example to DrawableFactory ;)

\$\endgroup\$

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.