如何编写抽象类构造函数,以便它可以灵活扩展子类 [英] How to write abstract class constructors so that it will be flexible for extending in sub classes

查看:234
本文介绍了如何编写抽象类构造函数,以便它可以灵活扩展子类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 抽象类Stack< T> {
factory Stack.empty()=> const _EmptyStack ._();

获取数据;
Stack< T>得到底部;
bool get isEmpty;
Stack< T> put(T item)=> new _StackImpl(item,this);
}

class _StackImpl< T>扩展Stack< T> {
final T _data;
final Stack< T> _底部;

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

T get data => _数据;
Stack< T> get bottom => _底部;
bool get isEmpty =>假;
}

class _EmptyStack< T>扩展Stack< T> {
const _EmptyStack ._();
T get data => throw new CollectionIsEmpty();
Stack< T> get bottom => throw new CollectionIsEmpty();
bool get isEmpty =>真正;
}

此代码在具体实现中引发两个错误:





我发现了一个示例代码,它似乎在这里解决这个问题,所以我已经通过在 Stack< T> 类中添加一个无参数的构造函数:

 抽象类Stack< T> {
Stack();
// ...

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

 不能调用'Stack< T>的非常量超级构造函数

c> Stack()构造函数阻止将该类用作mixin。



这些限制似乎强制类作者考虑如何该类将被扩展。 扩展列表 class dart:collection 包似乎确认这个结论 - 有一个完整的单独的类用于扩展,我不能直接扩展 List 类本身。



我的问题是更一般的,然后上述的问题:它可以灵活扩展吗?这包括允许使用以下功能:


  1. 超类中的工厂构造函数

  2. 正在构造函数


  3. const 用作mixin

虽然我理解使用mixin可能是不可能或不需要的,但其他点仍然有效。最重要的是,问题是:为什么我不能扩展一个具有工厂构造函数的类?这与我熟悉的任何其他OO语言不同。



还有相关问题:





编辑:感谢GünterZöchbauer答案我改进了代码,所以现在它是完全可操作的(见下文)。我现在剩下的最重要的问题是:为什么工厂构造函数打破了扩展类的能力?如何解决它(除了使用基类作为接口)?更简单的例子来说明:

  class Base {
}

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

一切都很好。但我们假设我及时回到我的 Base 类,并希望添加工厂方法:

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

现在每个扩展 Base 被破坏,因为未解析的隐式调用超级构造函数。我该怎么办?



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

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

获取数据;
Stack< T>得到底部;
bool get isEmpty;
Stack< T> put(T item)=> new _StackImpl(item,this);
}

class _StackImpl< T>扩展Stack< T> {
final T _data;
final Stack< T> _底部;

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

T get data => _数据;
Stack< T> get bottom => _底部;
bool get isEmpty =>假;
}

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

void main(){

group('stack',(){

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

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

test('添加到stack',(){

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

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

});

});

}


解决方案示例我建议只使用 Stack 作为接口而不是基类。


  1. 在超类中的工厂构造函数
    如果你有一个工厂构造函数,你必须添加一个正常的构造函数,如果你想如链接问题的答案中所述扩展。


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


  3. 在子类中的const构造函数
    如果你想要一个const构造函数,所有的子类都需要一个const构造函数太。
    在一个具有const构造函数的类中,所有字段都需要是final。这不是你的基类的情况,所以在 _EmptyStack 中添加一个const构造函数的地方。


  4. 用作mixin
    要用作mixin的类的限制是临时的,应该在某个时候删除。



I am trying to implement a persistent Stack data structure. I want to implement this as an algebraic data type, so it has two concrete subtypes: empty and non 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;
}

This code raises two errors in concrete implementations:

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

I found a sample code which seem to address this problem here, so I've fixed it by putting a parameterless constructor in Stack<T> class:

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

but now this causes problem with _EmptyStack<T> constructor, which is constant:

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

Additionally the added Stack() constructor prevents from using the class as a mixin.

These restrictions seem to enforce on the class author to think about how the class would be extended. The way of extending List class from dart:collection package seem to confirm this conclusion - there is an entire separate class to use for extension, I can't directly extend the List class itself.

My question is more general then the problem described above: how can I write a class so that it can be flexible enough to extend? That includes allowing the use of features like:

  1. factory constructors in super class
  2. normal constructors in sub class
  3. const constructors in sub class
  4. be used as a mixin

While I understand that the use as mixin might be impossible or even unwanted, other points are still valid. Most importantly the question stands: why can't I extend a class with a factory constructor? This is a behavior unlike any other OO language I'm familiar with.

Also related questions:

EDIT: Thanks to Günter Zöchbauer answer I've improved the code, so now it is fully operational (see below). The most important question that I am now left with is: why factory constructor breaks the ability to extend the class? And how to get around it (aside from using the base class as interface)? A simpler example to make the point:

class Base {
}

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

Everything is fine with this code. But let's say I get back to my Base class in time and want to add factory method:

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

Now each class which extends Base is broken because of unresolved implicit call to super constructor. What do I do then?

Corrected code from original question for reference:

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'));

    });

  });

}

解决方案

In your example I suggest to just use Stack as an interface instead of a base class.

  1. factory constructors in super class If you have a factory constructor you have to add a normal constructor too if you want to extend as stated in the answer to the linked question.

  2. normal constructors in sub class What was the actual question here? I guess this is the same as 1.

  3. const constructors in sub class If you want a const constructor all subclasses need to have a const constructor too. In a class with a const constructor all fields need to be final. This isn't the case with your base class so where is the point of adding a const constructor to _EmptyStack.

  4. be used as a mixin The restrictions for classes to be used as a mixin are temporary and should be removed at some point.

这篇关于如何编写抽象类构造函数,以便它可以灵活扩展子类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆