如何编写抽象类构造函数,以便它可以灵活地扩展到子类中

时间:2014-11-02 11:52:09

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

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

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<T>类中放置一个无参数构造函数来修复它:

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

但现在这会导致_EmptyStack<T>构造函数出现问题,这是常量:

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. 普通构造函数
  4. const子类
  5. 中的构造函数
  6. 用作mixin
  7. 虽然我知道混合使用可能是不可能的,甚至是不需要的,但其他要点仍然有效。最重要的是问题在于:为什么我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'));
    
        });
    
      });
    
    }
    

1 个答案:

答案 0 :(得分:1)

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

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

  2. 子类中的普通构造函数 这里的实际问题是什么?我想这与1相同。

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

  4. 用作mixin 对作为mixin使用的类的限制是暂时的,应该在某些时候删除。