Factory Constructor in Dart

Introduction

In this section, you will learn about factory constructors with examples. Before learning about factory constructors, you should have a basic understanding of class and objects, constructor, abstract class, interface and inheritance in Dart.

Factory Constructor In Dart

All of the constructors that you have learned until now are generative constructors. Dart also provides a special type of constructor called a factory constructor.

A factory constructor gives more flexibility to create an object. Generative constructors only create an instance of the class. But, the factory constructor can return an instance of the class or even subclass. It is also used to return the cached instance of the class.

Syntax

class ClassName {
  factory ClassName() {
    // TODO: return ClassName instance
  }

  factory ClassName.namedConstructor() {
    // TODO: return ClassName instance
  }
}

targets

Rules For Factory Constructors

  • Factory constructor must return an instance of the class or sub-class.
  • You can’t use this keyword inside factory constructor.
  • It can be named or unnamed and called like normal constructor.
  • It can’t access instance members of the class.

Example 1: Without Factory Constructor

In this example below, there is a class named Area with final properties length and breadth, and area. When you pass the length and breadth to the constructor, it calculates the area and stores it in the area property.

Info

Note: An initializer list allows you to assign properties to a new instance variable before the constructor body runs, but after creation.

class Area {
  final int length;
  final int breadth;
  final int area;

  // Initializer list 
 const Area(this.length, this.breadth) : area = length * breadth;
}

void main() {
  Area area = Area(10, 20);
  print("Area is: ${area.area}");

  // notice that here is a negative value
  Area area2 = Area(-10, 20);
  print("Area is: ${area2.area}");
}

targets

Show Output
Run Online

Here area2 object has a negative value. This is because we are not validating the input. Let’s create a factory constructor to validate the input.

Example 2: With Factory Constructor

In this example below, factory constructor is used to validate the input. If the input is valid, it will return a new class instance. If the input is invalid, then it will throw an exception.

class Area {
  final int length;
  final int breadth;
  final int area;

  // private constructor
  const Area._internal(this.length, this.breadth) : area = length * breadth;

  // Factory constructor
  factory Area(int length, int breadth) {
    if (length < 0 || breadth < 0) {
      throw Exception("Length and breadth must be positive");
    }
    // redirect to private constructor
    return Area._internal(length, breadth);
  }
}

void main() {
  // This works
  Area area = Area(10, 20);
  print("Area is: ${area.area}");

  // notice that here is negative value
  Area area2 = Area(-10, 20);
  print("Area is: ${area2.area}");
}

targets

Show Output
Run Online

Info

Note: With a factory constructor, you can initialize a final variable using logic that can’t be handled in the initializer list.

Example 3: Factory Constructor In Dart

In this example below, there is a class named Person with two properties, firstName and lastName, and two constructors, a normal constructor and a factory constructor. The factory constructor creates a Person object from a Map.

class Person {
  String firstName;
  String lastName;

  // constructor
  Person(this.firstName, this.lastName);

  // factory constructor Person.fromMap
  factory Person.fromMap(Map<String, Object> map) {
    final firstName = map['firstName'] as String;
    final lastName = map['lastName'] as String;
    return Person(firstName, lastName);
  }
}

void main() {
  // create a person object
  final person = Person('John', 'Doe');

  // create a person object from map
  final person2 = Person.fromMap({'firstName': 'Harry', 'lastName': 'Potter'});

  // print first and last name
  print("From normal constructor: ${person.firstName} ${person.lastName}");
  print("From factory constructor: ${person2.firstName} ${person2.lastName}");
}

targets

In the main method, two objects are created, one using the generative/normal constructor and the other using the factory constructor.

Show Output
Run Online

Example 4: Factory Constructor In Dart

In this example below, there is enum ShapeType with two values: circle and rectangle. There is an interface Shape with a factory constructor that creates objects of type Shape, either Circle or Rectangle. The main method instantiates two objects, one of each type, and calls the draw() method on each.

// enum ShapeType
enum ShapeType { circle, rectangle }

// abstract class Shape
abstract class Shape {
  // factory constructor
  factory Shape(ShapeType type) {
    switch (type) {
      case ShapeType.circle:
        return Circle();
      case ShapeType.rectangle:
        return Rectangle();
      default:
        throw 'Invalid shape type';
    }
  }
  // method
  void draw();
}

class Circle implements Shape {
  // implement draw method
  @override
  void draw() {
    print('Drawing circle');
  }
}

class Rectangle implements Shape {
  // implement draw method
  @override
  void draw() {
    print('Drawing rectangle');
  }
}

void main() {
  // create Shape object
  Shape shape = Shape(ShapeType.circle);
  Shape shape2 = Shape(ShapeType.rectangle);
  shape.draw();
  shape2.draw();
}

targets

Show Output
Run Online

Info

Note: Here it is possible to make List which contains both Circle and Rectangle objects in it.

Example 5: Factory Constructor In Dart

In this example below, there is class Person with a final field name. It also has a private constructor and a static _cache field. The class also has a factory constructor that checks if the _cache field contains a key that matches the name parameter. If it does, it returns the Person object associated with that key. Otherwise, it creates a new Person object, adds it to the _cache, and returns it.

class Person {
  // final fields
  final String name;

  // private constructor
  Person._internal(this.name);

  // static _cache field
  static final Map<String, Person> _cache = <String, Person>{};

  // factory constructor
  factory Person(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name]!;
    } else {
      final person = Person._internal(name);
      _cache[name] = person;
      return person;
    }
  }
}

void main() {
  final person1 = Person('John');
  final person2 = Person('Harry');
  final person3 = Person('John');

  // hashcode of person1 and person3 are same
  print("Person1 name is : ${person1.name} with hashcode ${person1.hashCode}");
  print("Person2 name is : ${person2.name} with hashcode ${person2.hashCode}");
  print("Person3 name is : ${person3.name} with hashcode ${person3.hashCode}");
}

targets

Show Output
Run Online

Singleton In Dart

Singletons are a common design pattern in object-oriented programming. A singleton class can have only one instance and provides a global point of access to it. You can create a singleton in Dart by defining a factory constructor that always returns the same instance. It is mostly useful when you want to create a single instance of a class and use it throughout the application like database connection app.

Example 6: Singleton Using Factory Constructor

This code creates a Singleton class that can only be instantiated once, and provides a factory constructor to get the instance of the class. The main method creates two objects of the Singleton class, and prints the hashcode of the objects to verify that they are same.

// Singleton using dart factory
class Singleton {
 // static variable
 static final Singleton _instance = Singleton._internal();
 
// factory constructor
 factory Singleton() {
   return _instance;
 }
 // private constructor 
 Singleton._internal();
}
 
void main() {
 Singleton obj1 = Singleton();
 Singleton obj2 = Singleton();
 print(obj1.hashCode);
 print(obj2.hashCode);
}
 

targets

Show Output
Run Online

You can see that both objects have the same hashcode. This is because both objects are pointing to the same instance.

Info

Note: Here Singleton._internal() is a private constructor so that it can not be called from outside the library. The factory constructor is used to return the same instance of the class.

Key Points

Here It means factory constructor

  • It uses the factory keyword to define a factory constructor.
  • It returns an instance of the same class or sub-class.
  • It is used to implement factory design patterns. [Return sub-class instance based on input parameter as shown in example 4]
  • It is used to implement singleton design patterns. [Return the same instance every time]
  • It is used to initialize a final variable using logic that can’t be handled in the initializer list.

Question For Practice

Create an interface called Bottle and add a method to it called open(). Create a class called CokeBottle and implement the Bottle and print the message “Coke bottle is opened”. Add a factory constructor to Bottle and return the object of CokeBottle. Instantiate CokeBottle using the factory constructor and call the open() on the object.

Video

Watch our video on factory constructor in Dart.