Search code examples
dartfactoryfinalgetter

When should final fields, factory constructors or private fields with getters be used in dart?


If you can figure out how to rename this question, I'm open for suggestions.

In the Dart language, one can write a class with final fields. These are fields that can only be set before the constructor body runs. That can be on declaration (usually for static constants inside a class), in an initialiser list syntax when declaring the constructor or using the this.field shorthand:

class NumBox{
  final num value;
  NumBox(this.value);
}

Let's say I actually needed to do some processing on instance creation and can't just initialise the field before the constructor. I can switch to using a private non-final field with a getter:

class NumBox{
  num _value;
  NumBox(num v) {
    _value = someComplexOperation(v);
  }
  num get value => _value;
}

Or I can get a similar behavior using a factory constructor:

class NumBox{
  final num value;
  factory NumBox(num v) {
    return new NumBox._internal(someComplexOperation(v));
  };
  NumBox._internal(this.value);
}

I hit a similar bump when I tried learning Dart a few years back and now that I have more baggage, I still don't know. What's the smarter way to do this?


Solution

  • You should design your API with your user in mind, then implement it in whatever way is simpler and more maintainable to you. This question is about the latter part.

    Making fields final is great when it's possible, and when it isn't, making them private with a public getter is a good alternative. It's your choice what to do, because it's you who is going to maintain your class, nobody else should need to look behind the public API.

    If you need a complex computation, Günther Zöchbauer's suggestion is the first to turn to: Use a helper function. In some cases, you can even do it inline

    class MyThing {
      final x;
      MyThing(args) : x = (() { complex code on args; return result;} ());
    }
    

    It gets ugly quickly, though, so having it as a static helper function is usually better.

    If your complex computation doesn't match this, which ususally means that there is more than one field being initialized with related values, then you need a place to store an intermediate value and use it more than once when initializing the object.

    A factory constructor is the easy approach, you can compute everything you need and then call the private generative constructore at the end. The only "problem" is that by not exposing a generative constructor, you prevent other people from extending your class. I quoted "problem" because that's not necessarily a bad thing - allowing people to extend the class is a contract which puts restrictions on what you can do with the class. I tend to favor public factory constructors with private generative constructors even when it's not needed for any practical reason, just to disable class extension.

    class MyClass {   
      const factory MyClass(args) = MyClass._; // Can be const, if you want it.
      const MyClass._(args) : ... init .. args ...;
    }
    

    If you need a generative constructor, you can use a forwarding generative constructor to introduce an intermediate variable with the computed value:

    class MyClass {
      final This x;
      final That y;
      MyClass(args) : this._(args, _complexComputation(args));
      MyClass._(args, extra) : x = extra.theThis, y = extra.theThat, ...;
    }
    

    All in all, there is no strict rule. If you prefer final fields, you can do extra work to make them final, or you can just hide the mutability behind a getter - it's an implementation and maintainability choice, and you're the one maintaining the code. As long as you keep the abstraction clean, and keeps track of what you have promised users (generative constructor? const constructor?) so you won't break that, you can change the implementation at any time.