Factory Design Pattern

Name: Factory Design Pattern

Type: Creational Design Pattern

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

Sample Problem and Solution:
Consider a situation where you are creating a library which provides some 2D Shapes. Other developers will use your library to create some shapes with random measurements. A naive way of designing the Shape classes 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 classes and the interface from the library. 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 unwanted complexity for the client developers to create some random shapes (Later, you will see the way to make our clients happy).

Additionally, all our 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 clients' code. For example, assume that you want 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 using Square class cannot be compiled anymore, because those Clients are tightly coupled with 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?
The factory design pattern is the solution to the above problem. Look at this new design which is using the 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();
    }
}
In this design, 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 (package private) access level (Only accessible within the package). The only way to create a Shape object is using the ShapeFactory by providing the required Shape type. According to this design, a client does not need to know the sub-classes. Even they do not need to worry about the logic behind object creation (They are not required to be an expert in geometry). Additionally, if you want to remove the Square class as mentioned earlier, it can be done easily without breaking 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 subclasses 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 *