Cidenet+Blog+Clean Dev Practices+Get an interface for object creation with Abstract Factory!

Get an interface for object creation with Abstract Factory!

What is it? What problem does it solve?

Abstract Factory provides an interface that allows creating related or dependent objects without specifying their concrete classes. Abstract Factory is a specialization of the Factory pattern, where there is no longer a Factory that creates a specific product given a parameter, but there are already multiple “factories” that create multiple products given a parameter.

For example, an application that has text fields, forms, etc., and each of these fields has different interfaces depending on the operating system where it runs, in this case, there will be as many factories as there is an operating system, and there will be a product for each element of the interface for each factory.

  • Abstract Factory: Declares an interface for abstract product creation operations.
  • Concrete Factory: Implements the operations to create concrete product objects.
  • Abstract Product: Declare an interface for a product type.
  • Concrete Product: Defines a product to be created by Concrete Factory.
  • Client: Use only interfaces declared by Abstract Factory and Abstract Product.

How to apply Abstract Factory?:

To apply this pattern, it is essential to know, at design time, what objects the application will have, their relationships, and what functions each of them will fulfill.

For this process, a matrix can be designed where the products can be related to their creators (the factories).

Continuing with the example of the graphical interface, it can be said that the program will be available for Windows and Linux. That the interface will have a text field and a button. The appearance of these fields are expected to conform to that of the operating system.

From this, it can be concluded that the products will be the respective fields and that there will be a factory for each operating system. In this way, each factory creates a field according to a platform.

Text field Button
Linux createLinuxTextField() createLinuxButton()
Windows createWindowsTextField() createWindowsButton()

As a code example, there is an application that does the following processes:

  • Do the linting process to a specific code.
  • Go through the process of formatting a code (beautify and uglify).

It should be noted that the application must carry out this process both to Java and JavaScript code.

Having this information, the following relationship can be made:

 

products / creator Products
Linter Formatter
Creator Java createJavaLinter() createJavaFormatter();
Javascript createJSLinter() createJSLinter();

Create Abstract Factory:

The abstract factory specifies the types of products that will be created. The factory selection method is also created by the client code.

public static AbstractFactory getFactory(EnumPlatform platform) {
   switch (platform) {
       case JAVA:
           return new JavaFactory();
       case JAVASCRIPT:
           return new JavaScriptFactory();

Create Abstract Product:

An abstract product must be created for each specific type of product in the relationship. These classes define the responsibilities that the product will have. In this case there are two Abstract products.

public abstract class Formatter {
   public abstract String beautify(String text);
   public abstract String uglify(String text);
}
public abstract class Linter {
   public abstract String lint(String text);
}

Create Productos concretos:

There must be a specific product for each factory / abstract factory. In this case, by having two factories and two products, there will be four specific products. It is in these classes where the logic is created for each platform / abstract product.

 

The following two images show a dummy implementation of the methods. Actually, in the JavaFormatter class, you must create the logic to embellish a string text to Java code and do the opposite process in the contiguous method. In the JavaScriptLinter class, you must write the logic to run the linting process to JavaScript code. The same must be done for JavaScriptFormatter and JavaLinter, but already writing the logic for the same processes, but depending on the language in question.

public class JavaFormatter extends Formatter {
   @Override
   public String beautify(String text) {
       return String.format("%s beautified with Java beautifier", text);
   }

   @Override
   public String uglify(String text) {
       return String.format("%s uglified with Java uglifier", text);
   }
}

public class JavaScriptLinter extends Linter {
   @Override
   public String lint(String text) {
       return String.format("%s linted with JavaScript linter", text);
   }
}

Create concrete factory:

Finally, a factory must be created for each platform, see an operating system, user type, etc. In this case, the platform is the language. There is a factory for the Java language and another for JavaScript. This is where you define what type of product should be instantiated.

Creators / Products Product
Linter Formatter
Creator Java createJavaLinter() createJavaFormatter();
Javascript createJSLinter() createJSLinter();

 

Creator / Products Products
Linter Formatter
Creator Java createJavaLinter() createJavaFormatter();
Javascript createJSLinter() createJSLinter();
public class JavaFactory extends AbstractFactory {
   @Override
   public Formatter createFormatter() {
       return new JavaFormatter();
   }

   @Override
   public Linter createLinter() {
       return new JavaLinter();
   }
}

public class JavaScriptFactory extends AbstractFactory {
   @Override
   public Formatter createFormatter() {
       return new JavaScriptFormatter();
   }

   @Override
   public Linter createLinter() {
       return new JavaScriptLinter();
   }
}

Calling the factories from the client:

Thanks to the decoupling achieved by the separation of platforms and products, the call is very simple. You just have to obtain an instance of the concrete factory that you want to use and through this instance call the concrete product that you want to obtain.

@Test
public void shouldGetJavaFormatter() {
assertEquals("text beautified with Java beautifier", AbstractFactory.getFactory(EnumPlatform.JAVA).createFormatter().beautify("text"));
assertEquals("text uglified with Java uglifier", AbstractFactory.getFactory(EnumPlatform.JAVA).createFormatter().uglify("text"));
}

@Test
public void shouldGetJavaLinter() {
assertEquals("text linted with Java linter", AbstractFactory.getFactory(EnumPlatform.JAVA).createLinter().lint("text"));
}

@Test
public void shouldGetJavaScriptFormatter() {
assertEquals("text beautified with Javascript beautifier", AbstractFactory.getFactory(EnumPlatform.JAVASCRIPT).createFormatter().beautify("text"));
assertEquals("text uglified with Javascript uglifier", AbstractFactory.getFactory(EnumPlatform.JAVASCRIPT).createFormatter().uglify("text"));
}

@Test
public void shouldGetJavaScriptLinter() {
assertEquals("text linted with JavaScript linter", AbstractFactory.getFactory(EnumPlatform.JAVASCRIPT).createLinter().lint("text"));
}

 

Add a new factory:

Suppose there is a new requirement to add the Python language, with this pattern it is easy to do this addition. A concrete factory must be created for this new language.

public class PythonFactory extends AbstractFactory {
   @Override
   public Formatter createFormatter() {
       return new PythonFormatter();
   }

   @Override
   public Linter createLinter() {
       return new PythonLinter();
   }
}

One class for each specific product:

public class PythonLinter extends Linter {
   @Override
   public String lint(String text) {
       return String.format("%s linted with Python linter", text);
   }
}

And add the new factory in the abstract factory to be used by the client. 

public static AbstractFactory getFactory(EnumPlatform platform) {
   switch (platform) {
       case JAVA:
           return new JavaFactory();
       case JAVASCRIPT:
           return new JavaScriptFactory();
       case PYTHON:
           return new PythonFactory();
       default:
           return new DefaultFactory();
   }
}

Conclusions:

  • If relationships between platforms and services can be represented by a matrix, this indicates that this pattern can be applied.
  • The class of a concrete factory appears only once in the application, in the abstract factory, which is where it is instantiated. This makes it easy to change the specific factory and application uses.
  • Different products can be used simply by changing the concrete factory, in the example, it is possible to change from a linter for Javascript to one for Java simply by changing the factory corresponding to Java.

Sample code can be found at https://bit.ly/3bEn9SZ

Sources:

Design Patterns : Elements of Reusable Object-Oriented Software

Contact us

Allow us to contact you

    Medellín - Colombia

  • Carrera 69 #49A - 11 Barrio Suramericana
  • (+57) 312 8475682
  • comunicaciones@cidenet.com.co

    United States

  • 1200 Colorado Blvd, Denver Colorado 80220
  • (+1) 7723619239
  • jceballos@cidenet.net
WhatsApp
>