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
私の質問は、上記の問題よりも一般的なものです。クラスを柔軟に拡張できるようにするには、どうすればクラスを作成できますか? これには、次のような機能の使用を許可することが含まれます。
- スーパークラスのファクトリーコンストラクター
- サブクラスの通常のコンストラクタ
const
サブクラスのコンストラクタ- ミックスインとして使用する
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'));
});
});
}