Yang Jiakang, CFUG community member, author of Journey of Flutter Development from South to North, xiaomi engineer

The term factory has cropped up frequently in topics surrounding design patterns, ranging from simple factory patterns to factory method patterns to abstract factory patterns. The meaning of factory name is the industrial place where products are manufactured. When applied in object oriented, it naturally becomes a more typical creation mode.

Formally, a factory can be a method/function that returns the object we want, an abstraction that acts as a constructor.

In this article, I’ll take you through the use of Dart to understand their respective implementations and their relationships.

Simple factory & Factory keyword

The simple Factory pattern is not one of the 23 GoF design patterns, but it is the programming approach we use most often. There is a special method that provides the desired instance object (object factory). We can put this method in a separate class, SimpleFactory, as follows:

class SimpleFactory {

  /// The factory method
  static Product createProduct(int type) {
    if (type == 1) {
      return ConcreteProduct1();
    }
    if (type == 2) {
      return ConcreteProduct2();
    }
    returnConcreteProduct(); }}Copy the code

We assume that the object to be created by this method belongs to the same Product class (abstract class) and specify the specific object type to be created with the parameter type. Dart does not support the interface keyword, but we can use Abstract to define the interface as an abstract class that each concrete type inherits and implements:

/// An abstract class
abstract class Product {
  String? name;
}

/// The implementation class
class ConcreteProduct implements Product {
  @override
  String? name = 'ConcreteProduct';
}

/// The implementation class 1
class ConcreteProduct1 implements Product {
  @override
  String? name = 'ConcreteProduct1';
}

/// The implementation class 2
class ConcreteProduct2 implements Product {
  @override
  String? name = 'ConcreteProduct2';
}
Copy the code

When we want to get the corresponding type object in our code, we just pass in the desired type value through this method, and we don’t have to worry about the logic of how production is produced and which object is selected:

void main() {
  final Product product = SimpleFactory.createProduct(1);
  print(product.name); // ConcreteProduct1
}
Copy the code

This is the simple factory pattern. At this point, we have to mention the factory keyword that is unique to Dart.

The factory keyword can be used to modify the Dart class constructor, meaning factory constructor. It can be used to make the class constructor act like a factory.

class Product {
  /// Factory constructor (modify create constructor)
  factory Product.createFactory(int type) {
    if (type == 1) {
      return Product.product1;
    } else if (type == 2) {
      return Product._concrete2();
    }
    return Product._concrete();
  }

  /// Named constructor
  Product._concrete() : name = 'concrete';

  /// Name constructor 1
  Product._concrete1() : name = 'concrete1';

  /// Name the constructor 2
  Product._concrete2() : name = 'concrete2';

  String name;
}
Copy the code

The factory constructor needs to return an object instance of the current class. We can call the corresponding constructor based on the argument to return the corresponding object instance.

void main() {
  Product product = Product.createFactory(1);
  print(product.name); // concrete1
}
Copy the code

In addition, the factory constructor does not require us to generate new objects every time. We can also pre-define some objects in the class for the factory constructor to use, so that each time we build an object with the same parameters, we will return the same object, as we saw in the singleton pattern section:

class Product {
  /// Factory constructor
  factory Product.create(int type) {
    if (type == 1) {
      return product1;
    } else if (type == 2) {
      return product2();
    }
    return Product._concrete();
  }

  static final Product product1 = Product._concrete1();
  static final Product product2 = Product._concrete2();
}
Copy the code

In addition to the named constructor, factory can also modify the default non-named constructor,

class Product {
  factory Product(int type) {
    return Product._concrete(); 
  }

  String? name;
}
Copy the code

One drawback of the factory constructor has been highlighted so far, namely that the user does not intuitively feel that he is using a factory function. The factory constructor is used in the same way as a normal constructor, but the instance produced by this constructor is equivalent to a singleton:

void main() {
  Product product = Product(1);
  print(product.name); // concrete1
}
Copy the code

Such usage can easily confuse users, so we should try to use a specific named constructor as the factory constructor (such as createFactory in the above example).

Factory method pattern

The factory method pattern is also one of the most commonly used tools in programming.

In a simple factory, the main object it serves is the customer, and the user of the factory method is irrelevant to the class of the factory itself, whereas the factory method pattern mainly serves its parent class, ProductFactory (analogous to Creator in UML) as follows:

/// The abstract factory
abstract class ProductFactory {
  /// Abstract factory method
  Product factoryMethod();

  /// Business code
  void dosomthing() {
    Product product = factoryMethod();
    print(product.name); }}Copy the code

In the ProductFactory class, the factoryMethod is an abstract method that each subclass must override and return a different Product object. When the dosomthing() method is called, it can respond differently depending on the returned object. Specific use methods are as follows:

/// The specific factory
class ProductFactory1 extends ProductFactory {
  
  /// Specific factory method 1
  @override
  Product factoryMethod() {
    returnConcreteProduct1(); }}class ProductFactory2 extends ProductFactory {
  /// Specific factory method 2
  @override
  Product factoryMethod() {
    returnConcreteProduct2(); }}/// use
main() {
  ProductFactory product = ProductFactory1();
  product.dosomthing();	// ConcreteProduct1
}
Copy the code

Abstract methods have a very practical application scenario in Flutter. We often need to take into account multi-platform adaptation when developing multiple applications with Flutter. In other words, the same operation can sometimes produce different results/styles on multiple platforms. We can place the logic for generating these different results/styles in a factory method.

As follows, we define a DialogFacory that can be used as a factory to generate dialogs with different styles:

abstract class DialogFacory {
  Widget createDialog(BuildContext context);

  Future<void> show(BuildContext context) async {
    final dialog = createDialog(context);
    return showDialog<void>(
      context: context,
      builder: (_) {
        returndialog; }); }}Copy the code

You can then create two dialogs with different styles for both Android and iOS platforms:

/// The Android platform
class AndroidAlertDialog extends DialogFactory {

  @override
  Widget createDialog(BuildContext context) {
    return AlertDialog(
      title: Text(getTitle()),
      content: const Text('This is the material-style alert dialog! '),
      actions: <Widget>[
        TextButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text('Close'),),,); }}/// The iOS platform
class IOSAlertDialog extends DialogFactory {
  
  @override
  Widget createDialog(BuildContext context) {
    return CupertinoAlertDialog(
      title: Text(getTitle()),
      content: const Text('This is the cupertino-style alert dialog! '),
      actions: <Widget>[
        CupertinoButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: const Text('Close'),),,); }}Copy the code

Now we can use the corresponding Dialog like this:

Future _showCustomDialog(BuildContext context) async {
  final dialog = AndroidAlertDialog();
  // final dialog = IOSAlertDialog();
  await selectedDialog.show(context);
}
Copy the code

The abstract factory

The main difference between the abstract factory pattern and the simple factory method is that the two patterns produce only one kind of object, whereas the abstract factory produces a series of objects (object families), and the generated series of objects must have some relationship. For example, Apple will produce mobile phones, tablets and other products, all of which belong to the Apple brand.

Like this abstract factory class:

abstract class ElectronicProductFactory {
  Product createComputer();
  
  Product createMobile();

  Product createPad();
  
  // ...
}
Copy the code

For Apple, I am the factory producing this kind of electronic products, so I can inherit this class and implement the method in it to produce various products:

class Apple extends ElectronicProductFactory {

  @override
  Product createComputer() {
    return Mac();
  }

  @override
  Product createMobile() {
    return IPhone();
  }

  @override
  Product createPad() {
    return IPad();
  }
  
  // ...
}
Copy the code

Similarly, huawei, Xiaomi and other electronic product manufacturers can also use the same way to express, which is the abstract factory mode.

When developing Flutter applications, we can also make full use of the abstract factory pattern to fit the application. We can define the following abstract factory for making widgets:

abstract class IWidgetsFactory {
  
  Widget createButton(BuildContext context);
  
  Widget createDialog(BuildContext context);
  
  // ...
}
Copy the code

Our applications often need to display different styles of widgets for each platform. Therefore, for each platform, we can implement the corresponding implementation factory, as follows:

/// Material style component factory
class MaterialWidgetsFactory extends IWidgetsFactory {
  @override
  Widget createButton(
      BuildContext context, VoidCallback? onPressed, String text) {
    return ElevatedButton(
      child: Text(text),
      onPressed: onPressed,
    );
  }

  @override
  Widget createDialog(BuildContext context, String title, String content) {
    return AlertDialog(title: Text(title), content: Text(content));
  }
  
  /// .
}

/// Cupertino style component factory
class CupertinoWidgetsFactory extends IWidgetsFactory {
  @override
  Widget createButton(
    BuildContext context,
    VoidCallback? onPressed,
    String text,
  ) {
    return CupertinoButton(
      child: Text(text),
      onPressed: onPressed,
    );
  }

  @override
  Widget createDialog(BuildContext context, String title, String content) {
    return CupertinoAlertDialog(
      title: Text(title),
      content: Text(content),
    );
  }
  
  // ...
}
Copy the code

So, if we use the MaterialWidgetsFactory on Android, and the CupertinoWidgetsFactory on iOS, we can use the widget for that platform, If you want to adapt to more platforms, you just need to inherit IWidgetsFactory to implement the factory class of the corresponding platform.

At this point, we can find that as a creational patterns, these three factory pattern main job is to create objects in different ways, but they have their own characteristics: simple factory pattern of abstract is the production of objects, factory method pattern is the abstract class methods, factory method pattern abstract object is the production factory, how to use different opinions.

Develop reading

  • Baidu Encyclopedia: Factory method pattern
  • Abstract Factory pattern
  • Mangirdas Kazlauskas: Flutter Design Patterns: Abstract Factory

About this series

The south to North (SHORT for Flutter Design Patterns) series is expected to be published every two weeks. It will introduce the common design patterns and development methods of Flutter application development to the developers. The purpose of this series is to promote the spread of Flutter/Dart language features. And to help developers develop high-quality, maintainable Flutter applications more efficiently.

I am happy to continue to improve the articles in this series. If you have any questions or suggestions on this article, please feel free to submit an issue to the official Github warehouse of the Chinese community or contact me directly. I will reply in time.