Using a factory constructor

Dart gives us many flexible and succinct ways to build objects through constructors:

  • With optional arguments, such as in the Person constructor where salary is optional:
    class Person{
      String name;
      num salary
      Person(this.name, {this.salary});
    } 
  • With named constructors (a bonus for readable self-documenting code), for example, where a BankAccount for the same owner as acc is created:
    BankAccount.sameOwner(BankAccount acc): owner = acc.owner;
  • With const constructors, as shown in the following code:
    class ImmutableSquare {
    finalnum length;
    static finalImmutableSquare ONE = const ImmutableSquare(1);
    constImmutableSquare(this.length);
    }

However, modern modular software applications require more flexible ways to build and return objects, often extracted into a factory design pattern (for more information, refer to http://en.wikipedia.org/wiki/Factory_(object-oriented_programming)). Dart has this pattern built right into the language with factory constructors.

How to do it...

In the factory program, we explore some usage examples as follows:

  1. Returning an object from a cache, as shown in the following code:
    main() {
      var sv = new Service('Credit Card Validation'),
      sv.serve('Validate card number'),
      Person p = new Person("S. Hawking");
      print(p); // S. Hawking
    }
    
    class Service {
      final String name;
      bool mute = false;
      // _cache is library-private, thanks to the _ in front of its name.
      static final Map<String, Service> _cache = <String, Service>{};
    
      Service._internal(this.name);
    
      factory Service(String name) {
        if (_cache.containsKey(name)) {
          return _cache[name];
        } else {
          final serv = new Service._internal(name);
          _cache[name] = serv;
          return serv;
        }
      }
    
      void serve(String msg) {
          if (!mute) {
            print(msg); // Validate card number
    
          }
      }
    }
  2. Creating an object from a subtype, as shown in the following code:
    class Person {
      factory Person(name) => new Teacher(name);
    }
    
    class Teacher implements Person {
      String name;
      Teacher(this.name);
      toString() => name;
    }

Using an abstract class, abstract_factory1-3 shows you how to use a factory constructor with it:

  • The factory constructor can be used with an abstract class, as shown in the following code:
    void main() {
      // factory as a default implementation of an abstract class:
       Cat cat = new Animal();
       var catSound = cat.makeNoise();
       print(catSound); // Meow
    }
    
    abstract class Animal {
       String makeNoise();
       factory Animal() => new Cat();
    }
    
    class Cat implements Animal { String makeNoise() => 'Meow'; }
    class Dog implements Animal { String makeNoise() => 'Woef';}
  • Another example of using the factory constructor with an abstract class is shown in the following code:
    import 'dart:math';
    
    void main() {
       Cat an = new Animal();
       print(an.makeNoise());
    }
    
    abstract class Animal {
       // simulates computation:
       factory Animal() {
           var random = new Random();
           if (random.nextBool())
             return new Cat();
           else
             return new Dog();
       }
    }
    
    class Cat implements Animal { String makeNoise() => 'Meow'; }
    class Dog implements Animal { String makeNoise() => 'Woef'; }
  • The next example also illustrates how to use a factory constructor with an abstract class:
    void main() {
       Cat cat = new Animal("cat");
       Dog dog = new Animal("dog");
       print(cat.makeNoise());
    }
    
    abstract class Animal {
       String makeNoise();
       factory Animal(String type) {
           switch(type) {
             case "cat":
               return new Cat();
             case "dog":
               return new Dog();
             default:
               throw "The '$type' is not an animal";
           }
         }
    }
    
    class Cat implements Animal { String makeNoise() => 'Meow'; }
    class Dog implements Animal { String makeNoise() => 'Woef'; }

How it works...

In the first example, we had a number of services gathered in _cache, but each service can be created only once through a private _internal constructor; every named service is unique.

In the second example, we showed that a class with a factory constructor cannot be directly extended; instead, the subtype must implement the class. In the third example, we showed three variants of using an abstract class with a factory constructor.

Why and when would you want to use a factory constructor? Sometimes, we don't want a constructor to always make a new object of the class it is in. The following are some use cases for a factory constructor; the code examples show you a usage example for all of them:

  • To return an object from a cache in order to reuse it
  • To create an object from a subtype of the class the constructor is in
  • To limit the instances to one unique object (the singleton pattern)
  • Even an abstract class can contain a factory constructor to return a default implementation of a concrete class (this is the only way an abstract class can have a constructor)

The factory constructor is invoked just as any other constructor by new. The consumer of the class doesn't know the constructor is really a factory, so you can refactor regular constructors into factory constructors to enhance flexibility without forcing clients to change their code. The factory could also involve much more preparation and computation, and the consumer of the class may not be aware of it; the consumer may just create a new instance. A factory invoking a private constructor is also a common pattern, as we saw in the first example.

Tip

The keyword this cannot be used inside a factory constructor because the constructor has no access to it.

There's more...

The factory constructor is also extensively used in standard libraries, for example, the DOM type CustomEvent only has factory constructors. This is because the browser has to produce these instances; the Dart object is just a wrapper. Another use case in Dart can be that you want to abstract an implementation of a certain feature. Some browsers support it natively, while others don't. You can then look into the factory to see whether the browser can handle it and then choose the right implementation according to the capabilities of the browser.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset