Factory Design Pattern

Name: Factory Design Pattern

Type: Creational Design Pattern

Purpose:
  • Hide object creation logic
  • Decouple classes from the clients

Sample Problem and Solution:
Consider a problem where you are required to create a library which provides some 2D Shapes. Other developers will use your library to create some shapes with random measurements. A possible design is provided here.
package com.javahelps.shapes;

public interface Shape {
    public void draw();
}
package com.javahelps.shapes;

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Circle");
    }
}
package com.javahelps.shapes;

public class Square implements Shape {
    private int width;

    public Square(int width) {
        this.width = width;
    }

    @Override
    public void draw() {
        System.out.println("Square");
    }
}
package com.javahelps.shapes;

public class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println("Rectangle");
    }
}
A sample client which is using this library:
import java.util.Random;
import com.javahelps.shapes.*;

public class ShapeClient {
    public static void main(String[] args) {
        Random random = new Random();
        Shape circle = new Circle(random.nextInt());
        Shape square = new Square(random.nextInt());
        Shape rectangle = new Rectangle(random.nextInt(), random.nextInt());
        circle.draw();
        square.draw();
        rectangle.draw();
    }
}
As you can see, client access all the classes and interface from the library in order to create the shape objects. In this example, the ultimate goal is: client needs some shapes with random sizes. However, client developers need to know that, for a circle they need to provide the radius. If they need a Square, the width is required and if they need a Rectangle both width and height are required. This is an unnecessary effort taken by client developers in order to create some random shapes (You will see later, the way to make our clients happy).

Additionally, all the classes are exposed to clients. It means, there is a tight coupling between Clients and the Shape library. Due to this tight coupling, library classes cannot be changed in future without breaking the clients. For example, assume that you plan to remove the Square class from your library because a Rectangle with same width and height can draw a Square. A modified Rectangle will be like this:
package com.javahelps.shapes;

public class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        if (width == height) {
            System.out.println("Square");
        } else {
            System.out.println("Rectangle");
        }
    }
}
BUT… if you remove the Square class, then the Clients will not work after the modification, because the Clients are highly depended on the Square class. Let’s define the flaw of this design.
  • Clients are required to have additional information about object creation
  • The library classes cannot be modified due to tight coupling
Then what is the good design which avoids these problems?
Factory design pattern is the solution for the given problem. Look at this new design which is using Factory design pattern.

package com.javahelps.shapes;

public interface Shape {
    public void draw();
}
package com.javahelps.shapes;

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Circle");
    }
}
package com.javahelps.shapes;

class Square implements Shape {
    private int width;

    public Square(int width) {
        this.width = width;
    }

    @Override
    public void draw() {
        System.out.println("Square");
    }
}
package com.javahelps.shapes;

class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println("Rectangle");
    }
}
The most important class ShapeFactory.
package com.javahelps.shapes;

import java.util.Random;

public class ShapeFactory {
    private Random random = new Random();
    public enum Type {
        CIRCLE, SQUARE, RECTANGLE;
    }

    public Shape createShape(Type type) {
        Shape shape = null;
        switch (type) {
        case CIRCLE:
            shape = new Circle(random.nextInt());
            break;
        case SQUARE:
            shape = new Square(random.nextInt());
            break;
        case RECTANGLE:
            shape = new Rectangle(random.nextInt(), random.nextInt());
        }
        return shape;
    }
}
The client which is using the ShapeFactory.
import com.javahelps.shapes.*;

public class ShapeClient {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape circle = factory.createShape(ShapeFactory.Type.CIRCLE);
        Shape square = factory.createShape(ShapeFactory.Type.SQUARE);
        Shape rectangle = factory.createShape(ShapeFactory.Type.RECTANGLE);
        circle.draw();
        square.draw();
        rectangle.draw();
    }
}
This time clients have access to Shape interface and ShapeFactory class only. Clients cannot access any of the sub classes of Shape interface, due to their default access level (Only accessible within the package). Only way to create a Shape object is using the ShapeFactory by providing the required Shape type. According to this design, client does not need to know about the sub classes. Even they do not need to worry about the logic behind object creation (They are not required to be expert in geometry). Additionally if you want to remove the Square class as suggested already, it can be done easily without affecting the client as shown below.
package com.javahelps.shapes;

class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        if (width == height) {
            System.out.println("Square");
        } else {
            System.out.println("Rectangle");
        }
    }
}
package com.javahelps.shapes;

import java.util.Random;

public class ShapeFactory {
    private Random random = new Random();
    public enum Type {
        CIRCLE, SQUARE, RECTANGLE;
    }

    public Shape createShape(Type type) {
        Shape shape = null;
        switch (type) {
        case CIRCLE:
            shape = new Circle(random.nextInt());
            break;
        case SQUARE:
            int w = random.nextInt();
            shape = new Rectangle(w, w);
            break;
        case RECTANGLE:
            shape = new Rectangle(random.nextInt(), random.nextInt());
        }
        return shape;
    }
}

Factory design pattern in Java API:

Quick recipe:
  1. Create a public interface for all your similar classes as the template.
  2. Try to keep all the sub classes in the default access level.
  3. Create a factory class with one or more than one methods to produce the objects with a return type of super interface..
Find the source codes at Git Hub.
Previous
Next Post »

Contact Form

Name

Email *

Message *