Understanding Factory constructor code example - Dart

ConstructorDart

Constructor Problem Overview


I have some niggling questions about factory constructors example mentioned here (https://www.dartlang.org/guides/language/language-tour#factory-constructors). I am aware of only three types of constructors on a basic level - default, named and parameterised.

  1. Why should I use factory at all for this example?
  2. Is that a named constructor which is being used? and why? Example factory constructor

Constructor Solutions


Solution 1 - Constructor

tl;dr Use a factory in situations where you don't necessarily want to return a new instance of the class itself. Use cases:

  • the constructor is expensive, so you want to return an existing instance - if possible - instead of creating a new one;
  • you only ever want to create one instance of a class (the singleton pattern);
  • you want to return a subclass instance instead of the class itself.

Explanation

A Dart class may have generative constructors or factory constructors. A generative constructor is a function that always returns a new instance of the class. Because of this, it does not utilize the return keyword. A common generative constructor is of the form:

class Person {
  String name;
  String country;

  // unnamed generative constructor
  Person(this.name, this.country);
}
var p = Person("...") // returns a new instance of the Person class

A factory constructor has looser constraints than a generative constructor. The factory need only return an instance that is the same type as the class or that implements its methods (ie satisfies its interface). This could be a new instance of the class, but could also be an existing instance of the class or a new/existing instance of a subclass (which will necessarily have the same methods as the parent). A factory can use control flow to determine what object to return, and must utilize the return keyword. In order for a factory to return a new class instance, it must first call a generative constructor.

Dart Factory Explained

In your example, the unnamed factory constructor first reads from a Map property called _cache (which, because it is Static, is stored at the class level and therefore independent of any instance variable). If an instance variable already exists, it is returned. Otherwise, a new instance is generated by calling the named generative constructor Logger._internal. This value is cached and then returned. Because the generative constructor takes only a name parameter, the mute property will always be initialized to false, but can be changed with the default setter:

var log = Logger("...");
log.mute = true;
log.log(...); // will not print

The term factory alludes to the Factory Pattern, which is all about allowing a constructor to return a subclass instance (instead of a class instance) based on the arguments supplied. A good example of this use case in Dart is the abstract HTML Element class, which defines dozens of named factory constructor functions returning different subclasses. For example, Element.div() and Element.li() return <div> and <li> elements, respectively.

In this caching application, I find "factory" a bit of a misnomer since its purpose is to avoid calls to the generative constructor, and I think of real-world factories as inherently generative. Perhaps a more suitable term here would be "warehouse": if an item is already available, pull it off the shelf and deliver it. If not, call for a new one.

How does all this relate to named constructors? Generative and factory constructors can both be either unnamed or named:

...
  // named generative
  // delegates to the default generative constructor
  Person.greek(String name) : this(name, "Greece"); 

  // named factory 
  factory Person.greek(String name) {
    return Greek(name);
  }
}

class Greek extends Person {
  Greek(String name) : super(name, "Greece");
}


Solution 2 - Constructor

  1. There is not much difference between a static method and a factory constructor.

For a factory constructor the return type is fixed to the type of the class while for a static method you can provide your own return type.

A factory constructor can be invoked with new, but that became mostly irrelevant with optional new in Dart 2.

There are other features like redirects rather rarely used that are supported for (factory) constructors but not for static methods.

It is probably still good practice to use a factory constructor to create instances of classes instead of static methods to make the purpose of object creation more obvious.

This is the reason a factory constructor is used in the example you posted and perhaps because the code was originally written in Dart 1 where it allowed to create a logger instance with new like with any other class.

  1. Yes this is a named constructor and the prefix _ makes it a private named constructor. Only named constructors can be made private because otherwise there would be no place to add the _ prefix.

It is used to prevent instance creation from anywhere else than from the public factory constructor. This way it is ensured there can't be more than one Logger instance in your application. The factory constructor only creates an instance the first time, and for subsequent calls always returns the previously created instance.

Solution 3 - Constructor

Complementing Dave's answer, this code shows a clear example when use factory to return a parent related class.

Take a look a this code from https://codelabs.developers.google.com/codelabs/from-java-to-dart/#3

You can run this code here. https://dartpad.dartlang.org/63e040a3fd682e191af40f6427eaf9ef

Make some changes in order to learn how it would work in certain situations, like singletons.

import 'dart:math';

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    // To trigger exception, don't implement a check for 'triangle'.
    throw 'Can\'t create $type.';
  }
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

class Triangle implements Shape {
  final num side;
  Triangle(this.side);
  num get area => pow(side, 2) / 2;
}

main() {
  try {
    print(Shape('circle').area);
    print(Shape('square').area);
    print(Shape('triangle').area);
  } catch (err) {
    print(err);
  }
}

Solution 4 - Constructor

In addition to the other answers, also consider the order of instantiating objects and when the instance is created:

In normal constructor, an instance gets created and the final variables get instantiated with the initializer list. This is why there's no return statement. The instance to return is already fixed, when executing the constructor!

In a factory constructor, the instance to return is decided by the method. That's why it needs a return statement and why it'll usually call a normal constructor in at least one path.

So a factory does nothing different than a static method could do (in other languages often called getInstance()), except you cannot overload the constructor with a static method but you can with a factory method. I.e. factory methods are a way to hide the fact that the user of your class is not calling a constructor but a static method:

// C++
MyCoolService.getInstance()

// Dart
MyCoolService()

Solution 5 - Constructor

Dart supports factory constructors, which can return subtypes or even null. To create a factory constructor, use the factory keyword:

class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

  factory Shape.fromTypeName(String typeName) {
    if (typeName == 'square') return Square();
    if (typeName == 'circle') return Circle();

    print('I don\'t recognize $typeName');
    return null;
  }
}

Code example

Fill in the factory constructor named IntegerHolder.fromList, making it do the following:

  • If the list has one value, create an IntegerSingle with that value.
  • If the list has two values, create an IntegerDouble with the values in order.
  • If the list has three values, create an IntegerTriple with the values in order.
  • Otherwise, return null.

Solution

class IntegerHolder {
  IntegerHolder();
  
  factory IntegerHolder.fromList(List<int> list) {
    if (list?.length == 1) {
      return IntegerSingle(list[0]);
    } else if (list?.length == 2) {
      return IntegerDouble(list[0], list[1]);
    } else if (list?.length == 3) {
      return IntegerTriple(list[0], list[1], list[2]);
    } else {
      return null;
    } 
  }
}

class IntegerSingle extends IntegerHolder {
  final int a;
  IntegerSingle(this.a); 
}

class IntegerDouble extends IntegerHolder {
  final int a;
  final int b;
  IntegerDouble(this.a, this.b); 
}

class IntegerTriple extends IntegerHolder {
  final int a;
  final int b;
  final int c;
  IntegerTriple(this.a, this.b, this.c); 
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAnanta K RoyView Question on Stackoverflow
Solution 1 - ConstructorDave FortView Answer on Stackoverflow
Solution 2 - ConstructorGünter ZöchbauerView Answer on Stackoverflow
Solution 3 - ConstructorMateus MeloView Answer on Stackoverflow
Solution 4 - Constructort.animalView Answer on Stackoverflow
Solution 5 - ConstructorParesh MangukiyaView Answer on Stackoverflow