【问题标题】:When should final fields, factory constructors or private fields with getters be used in dart?什么时候应该在 dart 中使用 final 字段、工厂构造函数或带有 getter 的私有字段?
【发布时间】:2018-01-29 23:51:07
【问题描述】:

如果你能弄清楚如何重命名这个问题,我愿意提供建议。

在 Dart 语言中,可以编写一个带有 final 字段的类。这些字段只能在构造函数主体运行之前设置。这可以在声明中(通常用于类中的静态常量),在声明构造函数时使用初始化列表语法或使用this.field 速记:

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

假设我实际上需要对实例创建进行一些处理,并且不能只在构造函数之前初始化字段。我可以切换到使用带有 getter 的私有非最终字段:

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

或者我可以使用工厂构造函数获得类似的行为:

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

几年前我尝试学习 Dart 时遇到了类似的问题,现在我有更多的包袱,我仍然不知道。 有什么更聪明的方法来做到这一点?

【问题讨论】:

    标签: dart factory final getter


    【解决方案1】:

    工厂构造函数是一种好方法,它允许不受限制地预先计算任何值,然后传递给普通构造函数以转发到最终字段。

    另一种方法是初始化列表,它在构造函数主体之前执行,因此允许初始化最终字段:

    class NumBox{
      final num value;
      NumBox(num v) : value = someComplexOperation(v)
    }
    

    在初始化列表中,您不能读取this,因为该实例尚未完全初始化。

    DartPad example

    【讨论】:

    • 我的逻辑很复杂,以至于我认为我不能把它放在初始化列表中。具有讽刺意味的是,看到你的回复让我意识到我的愚蠢示例正在引导我找到一个好的解决方案:从我的构造函数中提取逻辑到一个私有函数中,这样我就可以把它放在一个初始化列表中。
    【解决方案2】:

    您应该在设计 API 时考虑到您的用户,然后以任何对您来说更简单、更易于维护的方式来实现它。这个问题是关于后半部分的。

    在可能的情况下将字段设置为 final 是很好的,如果不是,使用公共 getter 将它们设为私有是一个不错的选择。做什么是你的选择,因为你要维护你的类,没有其他人需要查看公共 API 的背后。

    如果您需要复杂的计算,Günther Zöchbauer 的建议是第一个转向:使用辅助函数。在某些情况下,您甚至可以内联完成

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

    不过,它很快就会变得丑陋,因此将其作为静态辅助函数通常会更好。

    如果您的复杂计算与此不匹配,这通常意味着有多个字段正在使用相关值进行初始化,那么您需要一个地方来存储中间值并在初始化对象时多次使用它。

    工厂构造函数是一种简单的方法,您可以计算所需的一切,然后在最后调用私有生成构造函数。唯一的“问题”是通过不公开生成构造函数,您会阻止其他人扩展您的类。我引用了“问题”,因为这不一定是一件坏事——允许人们扩展课程是一种合同,它限制了你可以用课程做什么。 我倾向于使用私有生成构造函数的公共工厂构造函数,即使出于任何实际原因不需要它,只是为了禁用类扩展。

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

    如果您需要生成式构造函数,您可以使用转发生成式构造函数来引入具有计算值的中间变量:

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

    总而言之,没有严格的规定。如果您更喜欢 final 字段,您可以做额外的工作以使它们成为 final,或者您可以将可变性隐藏在 getter 后面 - 这是一种实现和可维护性的选择,您是维护代码的人。 只要您保持抽象干净,并跟踪您向用户承诺的内容(生成构造函数?const 构造函数?),这样您就不会破坏它,您可以随时更改实现。

    【讨论】:

    • 我的问题没有明确的解决方案,所以我会选择你的答案,因为它让我注意到不可能的工厂构造函数继承。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    • 2013-01-20
    相关资源
    最近更新 更多