【问题标题】:How to write abstract class constructors so that it will be flexible for extending in sub classes如何编写抽象类构造函数,以便在子类中灵活扩展
【发布时间】:2014-12-29 05:00:16
【问题描述】:

我正在尝试实现一个持久的Stack 数据结构。我想将其实现为代数数据类型,因此它有两个具体的子类型:emptynon empty

abstract class Stack<T> {
  factory Stack.empty() => const _EmptyStack._();

  T get data;
  Stack<T> get bottom;
  bool get isEmpty;
  Stack<T> put(T item) => new _StackImpl(item, this);
}

class _StackImpl<T> extends Stack<T> {
  final T _data;
  final Stack<T> _bottom;

  _StackImpl(T this._data, Stack<T> this._bottom);

  T get data => _data;
  Stack<T> get bottom => _bottom;
  bool get isEmpty => false;
}

class _EmptyStack<T> extends Stack<T> {
  const _EmptyStack._();
  T get data => throw new CollectionIsEmpty();
  Stack<T> get bottom => throw new CollectionIsEmpty();
  bool get isEmpty => true;
}

这段代码在具体实现中引发了两个错误:

[error] The class 'Stack' does not have a default generative constructor

我找到了一个看起来像address this problem here 的示例代码,所以我通过在Stack&lt;T&gt; 类中放置一个无参数构造函数来修复它:

abstract class Stack<T> {
    Stack();
    // ...

但现在这会导致 _EmptyStack&lt;T&gt; 构造函数出现问题,它是常量:

Constant constructor cannot call non-constant super constructor of 'Stack<T>'

另外添加的Stack() 构造函数可防止将类用作mixin。

这些限制似乎迫使课程作者考虑如何扩展课程。来自dart:collection 包的The way of extending List class 似乎证实了这个结论——有一个完整的单独类可用于扩展,我不能直接扩展List 类本身。

我的问题比上面描述的问题更笼统:我怎样才能编写一个类,以便它可以足够灵活地扩展?这包括允许使用以下功能:

  1. 超类中的工厂构造函数
  2. 普通子类中的构造函数
  3. const 子类中的构造函数
  4. 用作混合器

虽然我知道作为 mixin 的使用可能是不可能的,甚至是不需要的,但其他点仍然有效。最重要的问题是:为什么我不能extend 一个带有工厂构造函数的类?这是一种与我熟悉的任何其他 OO 语言不同的行为。

还有相关问题:

编辑:感谢Günter Zöchbauer answer,我改进了代码,现在它可以完全运行(见下文)。我现在留下的最重要的问题是:为什么工厂构造函数破坏了扩展类的能力?以及如何解决它(除了使用基类作为接口)?一个更简单的例子来说明这一点:

class Base {
}

class _Sub extends Base {
  int someValue;
  _Sub(int this.someValue);
}

这段代码一切正常。但是假设我及时回到我的Base 课程并想添加工厂方法:

class Base {
    factory Base.empty() => new _Sub(0);
}

现在每个扩展Base 的类都因为unresolved implicit call to super constructor 而被破坏。那我该怎么办?

原始问题的更正代码供参考:

abstract class Stack<T> {
  const Stack._();
  factory Stack.empty() => const _EmptyStack._();

  T get data;
  Stack<T> get bottom;
  bool get isEmpty;
  Stack<T> put(T item) => new _StackImpl(item, this);
}

class _StackImpl<T> extends Stack<T> {
  final T _data;
  final Stack<T> _bottom;

  _StackImpl(T this._data, Stack<T> this._bottom) : super._();

  T get data => _data;
  Stack<T> get bottom => _bottom;
  bool get isEmpty => false;
}

class _EmptyStack<T> extends Stack<T> {
  const _EmptyStack._() : super._();
  T get data => throw new CollectionIsEmpty();
  Stack<T> get bottom => throw new CollectionIsEmpty();
  bool get isEmpty => true;
}

void main(){

  group('stack', (){

    test('empty stack', (){
      var emptyStack = new Stack.empty();
      expect(emptyStack.isEmpty, isTrue);
      expect(() => emptyStack.data, throwsA(new isInstanceOf<CollectionIsEmpty>()));
      expect(() => emptyStack.bottom, throwsA(new isInstanceOf<CollectionIsEmpty>()));

      var emptyStack2 = new Stack.empty();
      expect(emptyStack == emptyStack2, isTrue);
    });

    test('adding to stack', (){

      var stack = new Stack<String>.empty().put("a").put("b").put("c");

      expect(stack.data, equals('c'));
      expect(stack.bottom.data, equals('b'));
      expect(stack.bottom.bottom.data, equals('a'));

    });

  });

}

【问题讨论】:

  • @GameAlchemist 设计是函数式编程的基本原则,它确实有效。有关该主题的更多信息,请参阅this answer。另外:正如我所说,我的问题比这个特殊情况更普遍。这只是一个例子。

标签: dart programming-languages language-design language-details


【解决方案1】:

在您的示例中,我建议只使用Stack 作为接口而不是基类。

  1. 超类中的工厂构造函数 如果你有一个工厂构造函数,如果你想按照链接问题的答案进行扩展,你也必须添加一个普通的构造函数。

  2. 子类中的普通构造函数 这里的实际问题是什么?我猜这和1一样。

  3. 子类中的 const 构造函数 如果你想要一个 const 构造函数,所有子类也需要有一个 const 构造函数。 在具有 const 构造函数的类中,所有字段都必须是 final 的。您的基类不是这种情况,那么将 const 构造函数添加到 _EmptyStack 的意义何在。

  4. 用作混合 将类用作 mixin 的限制是暂时的,应该在某个时候删除。

【讨论】:

  • 接口解决方案不够好。为什么我必须重新实现每个非抽象方法(DRY)?如果 interface 及时得到一个带有 body 的新方法怎么办?我必须找到它的所有实现并纠正它。抽象类不会出现这种情况。请查看我对问题的更新。
  • Ad 2 - 好的,我有点困惑,不知何故,我认为拥有 const Stack() 构造函数会阻止我拥有非 const _StackImpl() 构造函数,但它有效(参见更新)。广告 3 - 你的意思是不是所有的领域都是最终的? _StackImpl&lt;T&gt; 中只有两个字段,它们都是最终字段。基类没有字段,只有抽象 getter。它们算作非最终字段吗?
  • 抱歉,这是一个疏忽。我错过了这些只是吸气剂。
猜你喜欢
  • 2018-09-26
  • 1970-01-01
  • 2014-09-13
  • 2015-06-02
  • 1970-01-01
  • 2019-11-23
  • 2021-02-05
  • 1970-01-01
  • 2021-04-30
相关资源
最近更新 更多