1

Stack永続的なデータ構造を実装しようとしています。これを代数データ型として実装したいので、非空という 2 つの具体的なサブタイプがあります。

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;
}

このコードは、具体的な実装で 2 つのエラーを発生させます。

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

この問題に対処していると思われるサンプル コードを見つけたので、Stack<T>クラスにパラメーターなしのコンストラクターを配置して修正しました。

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

しかし今_EmptyStack<T>、これは定数であるコンストラクターに問題を引き起こします:

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

さらに、追加されたStack()コンストラクターにより、クラスを mixin として使用できなくなります。

これらの制限は、クラスの作成者がクラスを拡張する方法について考えるよう強制しているようです。パッケージからクラスを拡張する方法は、Listこの結論を裏付けているようです。拡張に使用する別のクラス全体があり、クラス自体dart:collectionを直接拡張することはできません。List

私の質問は、上記の問題よりも一般的なものです。クラスを柔軟に拡張できるようにするには、どうすればクラスを作成できますか? これには、次のような機能の使用を許可することが含まれます。

  1. スーパークラスのファクトリーコンストラクター
  2. サブクラスの通常のコンストラクタ
  3. constサブクラスのコンストラクタ
  4. ミックスインとして使用する

mixin としての使用が不可能であるか、望ましくないことさえあることは理解していますが、他の点は依然として有効です。最も重要な問題はextend、ファクトリ コンストラクタを持つクラスを作成できない理由です。これは、私がよく知っている他のオブジェクト指向言語とは異なる動作です。

関連する質問:

編集: 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は、 のために壊れてい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'));

    });

  });

}
4

1 に答える 1

1

あなたの例Stackでは、基本クラスの代わりにインターフェイスとして使用することをお勧めします。

  1. スーパークラスのファクトリーコンストラクターファクトリーコンストラクターがある場合、リンクされた質問への回答に記載されているように拡張したい場合は、通常のコンストラクターも追加する必要があります。

  2. サブクラスの通常のコンストラクター ここでの実際の質問は何でしたか? 1と同じだと思います。

  3. サブクラスの const コンストラクター const コンストラクターが必要な場合は、すべてのサブクラスにも const コンストラクターが必要です。const コンストラクターを持つクラスでは、すべてのフィールドが final である必要があります。これは基本クラスには当てはまらないため、に const コンストラクターを追加するポイントはどこにありますか_EmptyStack

  4. mixin として使用する クラスを mixin として使用するための制限は一時的なものであり、ある時点で削除する必要があります。

于 2014-11-02T14:50:27.747 に答える