【问题标题】:What's the purpose of allowing the declaration of an abstract method in a non-abstract class?允许在非抽象类中声明抽象方法的目的是什么?
【发布时间】:2017-08-17 14:34:16
【问题描述】:

根据this article,在 Dart 中可以定义一个非抽象类来拥有一个抽象(或未实现)方法。 抽象方法会引起警告,但不会阻止实例化。

允许在 Dart 的非抽象(或具体)类中声明抽象方法的目的是什么?为什么 Dart 会以这种方式工作?

【问题讨论】:

    标签: oop dart


    【解决方案1】:

    specification 实际上非常明确地说明了在具体类中声明抽象方法:

    如果抽象成员 m 被声明或在具体类中继承,则这是一个静态警告


    我们希望警告如果有人声明了一个具有抽象成员的具体类


    如果一个具体类有一个抽象成员声明或继承),这是一个静态警告

    他们没有任何预期的目的,这就是他们发出警告的原因。如果你熟悉Java:类似于accessing a static member via an object,同样没有意义,会触发警告。

    至于为什么它通过编译,Dart 使用了optional type system,这意味着输入概念不应该影响语言的语义,而这正是 Dart 强制执行的:

    抽象方法的目的是为类型检查和反射等目的提供声明。


    静态检查器会报告一些违反类型规则的行为,但这些违规行为不会中止编译或阻止执行。

    【讨论】:

      【解决方案2】:

      具体类中的抽象方法允许您为通过noSuchMethod() 实现的方法提供类型签名。提供 noSuchMethod() 实现也会使警告静音。

      在强模式下,简单地在具体类中包含抽象方法会导致错误,除非该类还实现了noSuchMethod() 接口。

      简而言之,具体类中抽象方法的目的是为noSuchMethod() 实现提供类型签名。这避免了调用未知方法和强模式(dartdevc 的默认设置,Dart 2.0 首先是默认设置,然后是强制模式)的警告,这些类型签名对于带有noSuchMethod() 的代码甚至编译都是必需的,除非目标是 dynamic 类型。

      例子:

      class A {
        void f();
        dynamic noSuchMethod(Invocation inv) => null;
      }
      
      void main() {
        var a = new A();
        a.f();
      }
      

      如果我们将a.f() 替换为(比如)a.f(0),那么这将导致错误(在强模式下),因为调用了错误数量的参数的方法。如果我们省略void f() 声明,那么我们将得到一个错误,即A 没有方法f()。如果我们省略 noSuchMethod() 实现,那么抱怨将是 f() 缺少方法体,即使 A 不是抽象的。

      下面的代码提供了一个更真实的例子:

      import "dart:mirrors";
      
      class DebugList<T> implements List<T> {
        List<T> _delegate;
        InstanceMirror _mirror;
        DebugList(this._delegate) {
          _mirror = reflect(_delegate);
        }
        dynamic noSuchMethod(Invocation inv) {
          print("entering ${inv.memberName}");
          var result = _mirror.delegate(inv);
          print("leaving  ${inv.memberName}");
          return result;
        }
      }
      
      void main() {
        List<int> list = new DebugList<int>([1, 2, 3]);
        int len = list.length;
        for (int i = 0; i < len; i++) print(list[i]);
      }
      

      此示例为List&lt;T&gt; 创建了一个调试装饰器,显示所有方法调用。我们使用implements List&lt;T&gt;拉入整个列表接口,继承了几十个抽象方法。当通过dartanalyzer 运行时,这通常会导致警告(或在强模式下,错误),因为我们缺少通常由List&lt;T&gt; 提供的所有这些方法的实现。提供 noSuchMethod() 实现可以消除这些警告/错误。

      虽然我们也可以手动包装所有 50 多个方法,但这将需要大量输入。如果将新方法添加到列表接口中而无需我们更改代码,上述方法也将继续有效。

      在具体类中显式列出方法的用例不太常见,但也可能出现。一个例子是向这样的调试装饰器添加 getter 或 setter,允许我们检查或设置委托的实例变量。无论如何,我们需要将它们添加到界面中,以避免使用它们时出现警告和错误;然后noSuchMethod() 实现可以使用getField()setField() 实现它们。这是上一个示例的变体,使用堆栈而不是列表:

      // main.dart
      
      import "dart:mirrors";
      import "stack.dart";
      
      class DebugStack<T> implements Stack<T> {
        Stack<T> _delegate;
        InstanceMirror _mirror;
        DebugStack(this._delegate) {
          _mirror = reflect(_delegate);
        }
      
        dynamic _get(Symbol sym) {
          // some magic so that we can retrieve private fields
          var name = MirrorSystem.getName(sym);
          var sym2 = MirrorSystem.getSymbol(name, _mirror.type.owner);
          return _mirror.getField(sym2).reflectee;
        }
      
        List<T> get _data;
      
        dynamic noSuchMethod(Invocation inv) {
          dynamic result;
          print("entering ${inv.memberName}");
          if (inv.isGetter)
            result = _get(inv.memberName);
          else
            result = _mirror.delegate(inv);
          print("leaving  ${inv.memberName}");
          return result;
        }
      }
      
      void main() {
        var stack = new DebugStack<int>(new Stack<int>.from([1, 2, 3]));
        print(stack._data);
        while (!stack.isEmpty) {
          print(stack.pop());
        }
      }
      
      // stack.dart
      
      class Stack<T> {
        List<T> _data = [];
        Stack.empty();
        Stack.from(Iterable<T> src) {
          _data.addAll(src);
        }
        void push(T item) => _data.add(item);
        T pop() => _data.removeLast();
        bool get isEmpty => _data.length == 0;
      }
      

      请注意,_data getter 的抽象声明对于类型检查至关重要。如果我们要删除它,即使没有强模式,我们也会收到警告,而在强模式下(例如,使用dartdevcdartanalyzer --strong),它会失败:

      $ dartdevc -o main.js main.dart
      [error] The getter '_data' isn't defined for the class 'DebugStack<int>' (main.dart, line 36, col 15)
      
      Please fix all errors before compiling (warnings are okay).
      

      【讨论】:

      • 这听起来像是对 noSuchMethod 的滥用/滥用 - 为什么要将 f 的实现放在 noSuchMethod 中,而不是简单地给 f 实现本身?
      • 举个简单的例子,考虑抽象方法没有显式列出的情况,而是来自implementsextends子句,noSuchMethod()代表继承接口的实际实现.
      • 您是说如果超类型强制子类型实施行为,noSuchMethod 可以委托给超类型的实现。你听不出来吗?即使SuperA 强制Sub 实现方法,而SuperB 持有实现,也不需要noSuchMethod。来自规范:“我们希望在声明具有抽象成员的具体类时发出警告。但是,类似下面的代码应该在没有警告的情况下工作”,然后是一个表达这一点的代码示例。问题是质疑明确在具体类上声明抽象方法。
      • 您的示例未声明抽象成员;它没有反映问题,它只显示了noSuchMethod 的示例,添加抽象成员不会改变任何内容。如果您添加了一个抽象成员并通过noSuchMethod 实现它,那么抽象方法not 就没有理由管理该实现。至于区别:“如果具体类具有抽象成员(声明或继承),则为静态警告” - “如果具体类具有抽象成员,则为静态警告抽象成员 m 在具体类中*声明或继承
      • 回复:“然后,您将解释如何使用 noSuchMethod,即使它适用于未在具体类(或基类)中声明的方法(抽象或非抽象)”。如果您不声明该方法,则在尝试调用它时会收到警告(或在强模式下会出现错误)。这就是即使在存在noSuchMethod() 的情况下也需要声明抽象方法的原因。
      猜你喜欢
      • 2016-05-10
      • 2011-01-23
      • 1970-01-01
      • 2012-09-21
      • 2012-09-16
      • 1970-01-01
      • 1970-01-01
      • 2017-05-13
      • 1970-01-01
      相关资源
      最近更新 更多