Flutter Design Patterns: 11 — Abstract Factory

An overview of the Abstract Factory design pattern and its implementation in Dart and Flutter

In the last article, I have analysed the Factory Method design pattern. This time I would like to analyse and implement an OOP design pattern, which has a similar purpose, even a similar name, but is more flexible and suits the structure of big projects better than the Factory Method design pattern — it is the Abstract Factory.


Table of Contents

  • Analysis
  • Implementation
  • Other articles in this series
  • Your contribution

What is the Abstract Factory design pattern?

Santa’s factory (source)

Abstract Factory is a creational design pattern, also known as Kit. Its intention in the GoF book is described as:

Provide an interface for creating families of related or dependent objects
without specifying their concrete classes.

The main purpose of the Abstract Factory design pattern is to encapsulate creating a family of objects in a separate factory object, hence abstracting the process of object creation. For all supported families of objects, a common interface for creating a family of objects is defined, then concrete factory class is created implementing this interface.

If you follow this series, I expect you have just had a sense of deja vu, haven’t you? The Factory Method design pattern has pretty much the same intention and purpose. Yes, it is true, simply like that. But why there is a separate factory pattern, then? The main difference between these two patterns is that the Abstract Factory pattern provides a way to create a family of related objects — a single factory is responsible for creating several objects. As a result, you don’t need to provide a separate factory for each specific class/component. In fact, you can consider the Factory Method design pattern as a subset of the Abstract Factory pattern — the Abstract Factory consists of several factory methods where each one of it creates only one specific object.

The Abstract Factory design pattern makes the creation of the objects more flexible:

  • Compile-time flexibility — the way objects are created can be implemented and changed independently from clients by defining new (sub)classes;
  • Run-time flexibility — a class can be configured with a factory object, which it uses to create objects, and even more, the factory object can be exchanged dynamically.

Finally, this pattern removes the direct binding of application-specific classes into the code. Correspondingly, the code only deals with interfaces of specific objects and their factories, but not with the specific implementations.

Let’s move to the analysis to understand the details of the Abstract Factory and how this design pattern could be implemented.


Analysis

Structure of the Abstract Factory design pattern (source)
  • Abstract Factory — declares an interface of operations that create abstract Product objects;
  • Concrete Factory — implements the operations to create Concrete Product objects. Each Concrete Factory corresponds only to a single variant of products;
  • Product — declares an interface for a type of Product object;
  • Concrete Product — implements the Product interface and defines a product object to be created by the corresponding Concrete Factory;
  • Client — uses only interfaces declared by the Abstract Factory and Product classes.

Applicability


Implementation

If you have read the last article, you should already be familiar with the problem that could be resolved by using the factory design pattern. If not, here is a short overview of the problem we will fix:

Even though you are using the same code base with Flutter, usually there is a demand that UI components should look different on different platforms. The simplest imaginable use case in Flutter context — showing the Material or Cupertino style widgets based on whether you are using accordingly an Android or iOS device.

Last time, we have resolved this issue by introducing the Factory Method design pattern to our code and providing a separate factory for each platform-specific component which creates the required widget dynamically after checking the current platform, hence separating the business logic from the representation layer (UI). However, this approach becomes a tremendous headache when multiple components exist which should look different on distinct platforms. Based on the Factory Method design pattern, every component should have a dedicated factory class to it and e.g. if you are creating an application for Android, iOS and Web, every new component would also require to add a new abstract class and 3 extra derived classes for the implementation in each specific platform.

Having the said problems in mind, the Abstract Factory design pattern is a better option than the Factory Method since only a single factory is needed per platform, a family of components are created and used together.

Class diagram

Class Diagram — Implementation of the Abstract Factory design pattern

IWidgetsFactory is an abstract class which is used as an interface for all the specific widget factories:

  • getTitle() — an abstract method which returns the title of the factory. Used in the UI;
  • createActivityIndicator() — an abstract method which returns the specific implementation (UI component/widget) of the activity (process) indicator implementing the IActivityIndicator interface;
  • createSlider() — an abstract method which returns the specific implementation (UI component/widget) of the slider implementing the ISlider interface;
  • createSwitch() — an abstract method which returns the specific implementation (UI component/widget) of the switch button implementing the ISwitch interface.

MaterialWidgetsFactory and CupertinoWidgetsFactory are concrete classes which implement the IWidgetsFactory class and its methods. MaterialWidgetsFactory creates Material style components (widgets) while the CupertinoWidgetsFactory creates Cupertino style widgets.

IActivityIndicator, ISlider and ISwitch are abstract classes which define the render() method for each component. These classes are implemented by both — Material and Cupertino — widgets.

AndroidActivityIndicator, AndroidSlider and AndroidSwitch are concrete implementations of the Material widgets implementing the render() method of corresponding interfaces.

IosActivityIndicator, IosSlider and IosSwitch are concrete implementations of the Cupertino widgets implementing the render() method of corresponding interfaces.

AbstractFactoryExample contains a list of factories implementing the IWidgetsFactory interface. After selecting the specific factory, the example widget uses its methods to create the corresponding widgets/UI components.

IWidgetsFactory

Widget factories

CupertinoWidgetsFactory — a concrete factory class which implements the IWidgetsFactory interface and its methods creating the Cupertino style widgets.

IActivityIndicator

Activity indicator widgets

IosActivityIndicator — a specific implementation of the activity indicator component returning the Cupertino style widget CupertinoActivityIndicator.

ISlider

Slider widgets

IosSlider — a specific implementation of the slider component returning the Cupertino style widget CupertinoSlider.

ISwitch

Switch widgets

IosSwitch — a specific implementation of the switch button component returning the Cupertino style widget CupertinoSwitch.

Example

AbstractFactoryExample contains a list of IWidgetsFactory objects (factories). After selecting the specific factory from the list, corresponding widgets are created using the factory methods and provided to the UI.

As you can see in the build() method, the example widget does not care about the selected concrete factory as long as it implements the IWidgetsFactory interface which methods return components implementing the corresponding common interfaces among all the factories and providing the render() methods used in the UI. Also, the implementation of the specific widgets is encapsulated and defined in separate widget classes implementing the render() method. Hence, the UI logic is not tightly coupled to any factory or component class which implementation details could be changed independently without affecting the implementation of the UI itself.

As you can see in the example, by selecting the specific platform widgets option, appropriate widgets are created by the factory methods and provided to the user.

All of the code changes for the Abstract Factory design pattern and its example implementation could be found here.



Your contribution

Flutter Community

Articles and Stories from the Flutter Community

Mangirdas Kazlauskas

Written by

Software Engineer | Flutter Enthusiast https://www.linkedin.com/in/mangirdas-kazlauskas/

Flutter Community

Articles and Stories from the Flutter Community

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade