Abstract factory design pattern in Java and Python

Creational Design Patterns provide solution to instantiate an object in the best possible way for specific situations. Following design patterns come under this category.

Abstract Factory pattern is a factory of factories. It groups the individual but related/dependent factories together without specifying their concrete classes.

An abstract factory is a factory that returns factories, don't worry about pun. Why is this layer of abstraction useful? A normal factory can be used to create sets of related objects. An abstract factory returns factories. Thus, an abstract factory is used to return factories that can be used to create sets of related objects.

Abstract factory design pattern is almost similar to factory pattern except the fact that it’s more like factory of factories. If you are familiar with factory design pattern, you will notice that we have a single Factory class that returns the different sub-classes based on the input provided and factory class uses if-else or switch statement to achieve this. In Abstract Factory pattern, we get rid of if-else block and have a factory class for each sub-class and then an Abstract Factory class that will return the sub-class based on the input factory class.

Benefits:

  • Abstract Factory pattern provides approach to code for interface rather than implementation.
  • Abstract Factory pattern is factory of factories (or super-factory) and can be easily extended to accommodate more products, for example we can add another sub-class Celerio and a factory MarutiFactory.
  • Abstract Factory pattern is robust and avoid conditional logic of Factory pattern.
dp_absfactory.jpg

For completeness, let's model the Clients interactions in a sequence diagram:

dp_absfactory_seq.png

Usage of Abstract factory pattern:

  • When the system needs to be independent of how its object are created, composed, and represented.
  • When the family of related objects has to be used together, then this constraint needs to be enforced.
  • When you want to provide a library of objects that does not show implementations and only reveals interfaces.
  • When the system needs to be configured with one of a multiple family of objects.

Java

Create an interface for Shapes.

// file Shape.java
public interface Shape {
   void draw();
}

Create concrete classes implementing the same interface.

// file Rectangle.java
public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

// file Square.java
public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

// file Circle.java
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

Create an interface for Colors.

// file Color.java
public interface Color {
   void fill();
}

Create concrete classes implementing the same interface.

// file Red.java
public class Red implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Red::fill() method.");
   }
}

// file Green.java
public class Green implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

// file Blue.java
public class Blue implements Color {
   @Override
   public void fill() {
      System.out.println("Inside Blue::fill() method.");
   }
}

Create an Abstract class to get factories for Color and Shape Objects.

// file AbstractFactory.java
public abstract class AbstractFactory {
   abstract Color getColor(String color);
   abstract Shape getShape(String shape) ;
}

Create Factory classes extending AbstractFactory to generate object of concrete class based on given information.

// file ShapeFactory.java
public class ShapeFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }     
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      }else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      }else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }

      return null;
   }

   @Override
   Color getColor(String color) {
      return null;
   }
}

// file ColorFactory.java
public class ColorFactory extends AbstractFactory {
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   @Override
   Color getColor(String color) {
      if(color == null){
         return null;
      }     
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      }else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      }else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }

      return null;
   }
}

Create a Factory generator/producer class to get factories by passing an information such as Shape or Color

// file FactoryProducer.java

public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      }else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

Use the FactoryProducer to get AbstractFactory in order to get factories of concrete classes by passing an information such as type.

// file AbstractFactoryPatternDemo.java

public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");

      Shape shape1 = shapeFactory.getShape("CIRCLE");
      shape1.draw();

      Shape shape2 = shapeFactory.getShape("RECTANGLE");
      shape2.draw();

      Shape shape3 = shapeFactory.getShape("SQUARE");
      shape3.draw();

      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");

      Color color1 = colorFactory.getColor("RED");
      color1.fill();

      Color color2 = colorFactory.getColor("Green");
      color2.fill();

      Color color3 = colorFactory.getColor("BLUE");
      color3.fill();
   }
}

Python 3

import abc

# create an interface for Shapes
class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def draw(self):
        pass

# create an interface for Colors
class Color(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def fill(self):
        pass


# create an abstract class to get factories for Color and Shape objects
class AbstractFactory(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get_color(self):
        pass

    @abc.abstractmethod
    def get_shape(self):
        pass


class Rectangle(Shape):
    def draw(self):
        print("Inside Rectangle::draw() method.")

class Square(Shape):
    def draw(self):
        print("Inside Square::draw() method.")

class Circle(Shape):
    def draw(self):
        print("Inside Circle::draw() method.")


class Red(Color):
    def fill(self):
        print("Inside Red::fill() method.")

class Green(Color):
    def fill(self):
        print("Inside Green::fill() method.")

class Blue(Color):
    def fill(self):
        print("Inside Blue::fill() method.")


# create Factory classes extending AbstractFactory 
# to generate object of concrete class based on given information.
class ShapeFactory(AbstractFactory):
    def get_shape(self, shape_type):
        if shape_type == None:
            return None

        if shape_type == "CIRCLE":
            return Circle()
        elif shape_type == "RECTANGLE":
            return Rectangle()
        elif shape_type == "SQUARE":
            return Square()

        return None

    def get_color(self):
        return None


class ColorFactory(AbstractFactory):
    def get_color(self, color_type):
        if color_type == None:
            return None

        if color_type == "RED":
            return Red()
        elif color_type == "GREEN":
            return Green()
        elif color_type == "BLUE":
            return Blue()

        return None

    def get_shape(self):
        return None


# create a Factory generator/producer class 
# to get factories by passing an information such as Shape or Color

class FactoryProducer:
    @staticmethod
    def get_factory(choice):
        if choice == "SHAPE":
            return ShapeFactory()
        elif choice == "COLOR":
            return ColorFactory()
        return None


if __name__ == '__main__':
    shape_factory = FactoryProducer.get_factory("SHAPE")

    shape1 = shape_factory.get_shape("CIRCLE");
    shape1.draw()

    shape2 = shape_factory.get_shape("RECTANGLE");
    shape2.draw()

    shape3 = shape_factory.get_shape("SQUARE");
    shape3.draw()

    color_factory = FactoryProducer.get_factory("COLOR");

    color1 = color_factory.get_color("RED");
    color1.fill()

    color2 = color_factory.get_color("GREEN");
    color2.fill()

    color3 = color_factory.get_color("BLUE");
    color3.fill()
comments powered by Disqus