Complete Guide: Abstract Factory Design Pattern in Dart

Software design patterns are reusable solutions to commonly occurring problems in the software development world. These patterns provide a way for developers to structure their code in a consistent and organized manner, making it easier to write maintainable and scalable code. Design patterns provide a common language for developers to communicate and collaborate on software projects, helping to improve the overall quality of the code. They are also highly adaptable and can be applied to a wide range of programming languages and environments. Design patterns provide a blueprint for solving complex software development problems, and they have been widely adopted and used by developers for many years. Some of these patterns provide solutions related to object creation, while others provide mechanisms for objects communication and compositions.

In this post, we’ll take an in-depth look at the abstract factory design pattern in dart which is a creational pattern that helps creating objects at ease. You’ll be able to understand what abstract factory pattern is, when and why to use it, practice an example, and see its pros and cons.

What is Abstract Factory Design Pattern?

According to the book “Design Patterns Elements of Reusable Object-Oriented Software“, abstract factory is an interface for creating families of related or dependent objects without specifying their concrete classes.

This pattern is a super set extension of Factory Method pattern, which provides a way to create objects of a single type. It’s like a factory of the factory. It defines an interface that creates objects in a super-factory, which may be used as a factory of factories. Each created factory can instantiate objects as per the factory pattern.

Let’s reconsider the chef analogy I used in the previous article on factory method. In case you haven’t read that, let me quickly recap the analogy to you. The chef represents the factory that uses different recipes and ingredients(abstract classes and methods) to cook different dishes (objects) such as pizza, spaghetti, and roasted chicken. Let’s focus on Pizza this time and say that the chef prepares different pizza types based on different cuisines. For example, imagine the restaurant serves Italian and French Pizza instead of just a single type of pizza. Each type of pizza has several variants such as pepperoni an vegetables.

Just as a chef has a menu with different pizzas from different cuisines, the Abstract Factory pattern has a menu of different families of objects. For example, one pizza type could be a factory for creating pizzas for an Italian cuisine and another for creating pizzas for a French cuisine.

When a customer orders a pizza, the chef uses the ingredients and recipes to cook it based on the way the specific cuisines serves it. In the same way, when a client requests an object from the Abstract Factory, the factory uses the appropriate concrete factory to create the requested object.

abstract factory design pattern in dart
A normal day at pizza restaurant

This pattern provides a way to encapsulate a family of individual factories that have a common theme, without specifying their concrete classes. This allows for flexibility and the ability to change the concrete classes used to create objects at runtime.

The Abstract Factory pattern is particularly useful in situations where a system needs to be independent of the way its objects are instantiated, composed, and represented. It allows for the creation of objects that belong to a family of related objects, without specifying their concrete classes.

Components of Abstract Factory Pattern

The Abstract factory design pattern consists of the following main components:

  1. Abstract Factory class: this is an interface that has the methods to create the abstract product. (The Pizza Factory)
  2. Concrete factory class: implements the methods of the abstract factory. Each concrete factory creates an only single variant of the object. (Italian pizza Factory or French Pizza Factory).
  3. abstract product: a class for a type of object (Pizza)
  4. Concrete product class: implements the product interface and defines an object to be created by the corresponding factory. (French Pepperoni, French Vegetable, Italian Pepperoni, Italian Vegentable)
  5. Client class: uses the abstract factory and product interfaces to create the objects. The Client does not know the concrete class of the objects it creates.
illustration of abstract factory components
illustration of abstract factory components

Abstract Factory Design Pattern Example in Dart

Abstract factory

abstract class PizzaFactory {
  void pickPizza(String type);
}

Concrete factories

//Italian pizza factory
class ItalianPizzaFactory implements PizzaFactory{

  @override
  Pizza pickPizza(String type) {
    switch(type) {
      case 'Pepperoni':
        return ItalianPepperoniPizza();
      case 'Vegetable':
        return ItalianVegetablePizza();

      default:
        throw Exception('Invalid Pizza Type');
    }
  }

}

//French Pizza factory
class FrenchPizzaFactory implements PizzaFactory{

  @override
  Pizza pickPizza(String type) {
    switch(type) {
      case 'Pepperoni':
        return FrenchPepperoniPizza();
      case 'Vegetable':
        return FrenchVegetablePizza();

      default:
        throw Exception('Invalid Pizza Type');
    }
  }

}

Abstract product

//abstract product
abstract class Pizza {
  void prepare();
}

Concrete products

//concrete product1: Italian pepperoni pizza
class ItalianPepperoniPizza implements Pizza{
  @override
  void prepare() {
    print('preparing Italian Pepperoni Pizza');
  }

}

//concrete product2: Italian vegetable pizza
class ItalianVegetablePizza implements Pizza{
  @override
  void prepare() {
    print('preparing Italian Vegetable Pizza');
  }

}

//concrete product3: French pepperoni pizza
class FrenchPepperoniPizza implements Pizza{
  @override
  void prepare() {
    print('preparing French Pepperoni Pizza');
  }

}
//concrete product4: French Vegetable pizza
class FrenchVegetablePizza implements Pizza{
  @override
  void prepare() {
    print('preparing French Vegetable Pizza');
  }

}

Client code

void main() {
    final italianPepPizza = ItalianPizzaFactory().pickPizza('Pepperoni').prepare();
    final italianVegPizza = ItalianPizzaFactory().pickPizza('Vegetable').prepare();
    final frenchPepPizza = FrenchPizzaFactory().pickPizza('Pepperoni').prepare();
    final frenchVegPizza = FrenchPizzaFactory().pickPizza('Vegetable').prepare();

  }

When to use Abstract Factory pattern?

This pattern is best used when you have a family of related objects in different variants. In fact, one common usage for the abstract factory is in rendering Flutter widgets. Since Flutter is a multi-platform framework, it displays widgets differently on each of them. for example, progress bar looks different on Android and iOS and abstract factory is used to determine the widget to display based on the platform the user’s on.

Advantages / Disadvantages

Abstract factory is a powerful pattern that can brings benefits in variety of aspects:

  1. Encapsulation of object creation: abstract factory encapsulates the details behind the object creation process which makes is easier to change the way these objects are created without affecting the existing code.
  2. Improved Flexibility: Since this is a method to create families of related objects, swapping between objects families at runtime is made easy. By providing a standard interface for creating families of related objects, the Abstract Factory pattern can make it easier to add new families of objects without having to modify existing code. This can make code more flexible and adaptable to changing needs.
  3. Improved Maintainability: the encapsulation of object creation and separating it from the code which uses these objects makes the code more modular and easier to maintain. For example, if you decide to change the way a particular family of objects is created, you can do so without modifying the code that uses those objects. This can be especially useful in situations where you have a large code base with many different classes that use the same family of objects.
  4. Standardization: the abstract factory provides a standard interface for creating groups of related objects which can be useful in situations where you want to make sure that all objects belonging to one family have a consistent set of functions and properties.

On the other hand, this pattern has some disadvantages as well:

  1. Complexity: this pattern requires creating of many classes including lots of products families and the complexity increases as the project grows. As a result, this can make the code harder to understand and read hence harder to modify and maintain in the future.
  2. Limited extensibility: abstract factory is designed to create families of related objects but it might not suit objects which don’t fit in a specific family. This could lead to creating separate factories for these objects making the code more complicated.
  3. Overhead: Because the abstract factory method creates an additional layer of abstraction to initialize objects. This could lead to creating some overhead associated with object creation. This can result in a performance penalty, particularly if the factory is called frequently.

Conclusion

In conclusion, the abstract factory pattern is useful for creating groups of related objects in a very maintainable and flexible way. This pattern can encapsulates the object creation part and improve the overall project’s maintainability, and flexibility. However, this patterns till has its drawbacks as it may complicate the code and cause some overhead because of the additional layer of abstraction, as well as limiting the extensibility of the code for objects not belonging to the families.

When it comes to design patterns, it’s important to analyze each pattern and evaluate the best use cases for each of them. This helps you build a solid understanding of the role each plays which helps you decide which one to use based on your situation.

Please don’t hesitate to support this blog with a simple comment or share if you find the content helpful. Your support is highly appreciated and means the world to me. 🙂

Thank you for reading and happy coding!

Oh hi there!
It’s nice to meet you.

Sign up to receive awesome content in your inbox, every month.

Let's Do This!

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top