C#の継承ツリーと保護されたコンストラクター
-
03-07-2019 - |
質問
次の継承ツリーを考えると、機能するように実装する最善の方法は何ですか?
abstract class Foo<T> : IEnumerable<T>
{
public abstract Bar CreateBar();
}
class Bar<T> : Foo<T>
{
// Bar's provide a proxy interface to Foo's and limit access nicely.
// The general public shouldn't be making these though, they have access
// via CreateBar()
protected Bar(Foo base)
{
// snip...
}
}
class Baz<T> : Foo<T>
{
public Bar CreateBar()
{
return new Bar(this);
}
}
これは失敗します: 'Bar.Bar()'は保護レベルのためアクセスできません
。
コンストラクタをパブリックにしたくありません。 Foo
を継承するクラスのみが Bar
を作成できます。 Bar
は特殊な Foo
であり、どのタイプの Foo
でも作成できます。ここでは、パブリック内部は「オプション」です。これは、 Foo
の定義済みの拡張機能の大部分がDLLの内部にあるためですが、後で作成したい人は独自のタイプの Foo
または Baz
(これが発生する可能性が高い)は、デフォルトの CreateBar()
実装でスタックします。彼らのニーズを満たしていない。
おそらく、これをリファクタリングしてうまく機能させる方法はありますか?私はこれを設計しようとして壁に頭を叩いているので、それでも動作します。
編集(詳細):
もう少し具体的に:FooはIEnumerableを実装しており、短い話ですが、Barは同じインターフェイスを提供していますが、その列挙可能なオブジェクトの限定されたサブセットに提供しています。すべてのFooは、自身のサブセット(バーなど)を作成して返すことができるはずです。しかし、Fooを実装したいすべての人がこれを心配する必要はありません。Barがプロキシを実行し、範囲の制限などを心配するからです。
解決
さて、新しい答え:
- バーをインターフェイスと具象クラスに分割します。
- パブリックな抽象メソッドをIBarで表現します。
- バーをFooのネストされたプライベートクラスにし、IBarを実装します。 Fooから呼び出すことができる内部コンストラクターを指定します。
- 自身からBarのインスタンスを作成するFooで保護されたメソッドを記述します。 Fooから派生したクラスは、プロキシを使用するだけで十分な場合にこれを使用して抽象メソッドを実装でき、より複雑なニーズを持つクラスはIBarを直接実装できます。抽象メソッドを仮想メソッドに変更し、「this」から新しいバーを作成することもできます。デフォルトで。
編集:これの1つのバリアントは、パブリックコンストラクターを使用して、BarをFoo内の保護ネストクラスにすることです。そうすれば、派生クラスはそれ自体をインスタンス化できますが、無関係なクラスは「見る」ことができません。まったく。インターフェースを実装から分離する必要があります(インターフェースを公開できるようにするため)。とにかくそれは良いことだと思います。
他のヒント
BazをBar内の入れ子型にすることは可能でしょうか?これが、他の方法よりもBarにアクセスできるようにする唯一の方法です。同じ親クラスを持つだけで、Fooの保護されたメンバーにのみアクセスでき、FooにはBarへの特別なアクセス権がありません。ネストされた型でこれを行う他の曲がりくねった方法があると思いますが、実際にはメンテナンスエンジニアにとっては非常に不愉快になります。
ただし、1つの派生クラスに、同じ基本クラスから派生した異なるクラスのインスタンスを作成させることは、非常に奇妙な設計です。それは本当にあなたが必要なものですか?おそらく、これをより具体的な用語で言えば、代替デザインを思い付くのが簡単になるでしょう。
Foo内のネストされた型を介してBarのコンストラクタにアクセスできます:
abstract class Foo<T> : IEnumerable<T>
{
public abstract Bar<T> CreateBar();
protected Bar<T> CreateBar(Foo<T> f) { return new FooBar(f); }
private class FooBar : Bar<T>
{ public FooBar(Foo<T> f) : base(f) {}
}
}
class Bar<T> : Foo<T>
{ protected Bar(Foo<T> @base) {}
}
class Baz<T> : Foo<T>
{
public override Bar<T> CreateBar()
{
return CreateBar(this);
}
}
BarがFooから派生していることはしばらく忘れてください。これを行うと、サブクラスがBarのコンストラクターにアクセスできない場合でも、FooのすべてのサブクラスがBarを作成できるようにするにはどうすればよいですか?
それは解決が非常に簡単な問題です:
public class Foo
{
protected static Bar CreateBarInstance()
{
return new Bar();
}
public virtual Bar CreateBar()
{
return CreateBarInstance();
}
}
public class Bar
{
internal Bar()
{
}
}
public class Baz : Foo
{
public override Bar CreateBar()
{
Bar b = base.CreateBar();
// manipulate the Bar in some fashion
return b;
}
}
FooのサブクラスがBarのコンストラクターにアクセスできないことを保証したい場合は、それらを別のアセンブリに配置します。
今、FooからBarを派生させるのは簡単な変更です:
public class Bar : Foo
{
internal Bar()
{
}
public override Bar CreateBar()
{
throw new InvalidOperationException("I'm sorry, Dave, I can't do that.");
}
}
&quot;コンストラクタをパブリックにしたくない。&quot;確認してください。
&quot; Fooを継承するクラスのみがバーを作成できます。&quot;確認してください。
&quot;どのタイプのFooでも作成できます。&quot;確認してください
&quot;自分のタイプのFooまたはBazを作成したいと思う人(後で発生する可能性が高い)は、デフォルトのCreateBar()実装に固執します。これは、Foo.CreateBar()メソッドで何を行う必要があるかに大きく依存しています。
C#は、C ++ friendキーワードに直接相当するものを提供しません。あなたのデザインがこの種の構造を必要としているようです。
C ++では、&quot; friend&quot;を使用して、特定のクラスが別のクラスのプライベート/保護されたメンバーにアクセスできることを指定できます。注:これは、同じアセンブリ内のすべてのクラスへのアクセスを提供するC#internal修飾子とは異なります。
あなたのデザインを見ると、C ++スタイルの友人を必要とする線に沿って何かをしようとしているようです。 Jon Skeetは正しい、これを補うC#の通常の設計は、ネストされたクラスを使用することです。
このフォーラムの投稿でさらに説明し、これを行う方法の例を示します。