オブジェクト指向プログラミングを使用すると、クラス(入れ子クラス)内にクラスを作成することができますが、4年間のコーディング経験で入れ子クラスを作成したことはありません。
ネストされたクラスは何に適していますか?
クラスがネストされている場合、クラスをプライベートとしてマークできることと、包含クラスからそのクラスのすべてのプライベートメンバーにアクセスできることを知っています。含まれているクラス自体に変数をプライベートとして配置するだけです。
では、なぜネストされたクラスを作成するのですか?
ネストされたクラスはどのシナリオで使用する必要がありますか、それとも他の手法よりも使用法が強力ですか?
ネストされたクラスの主な機能は、クラス自体の全機能を持ちながら、外部クラスのプライベートメンバーにアクセスできることです。また、特定の状況でかなり強力なカプセル化を可能にするプライベートにすることもできます。
ここでは、クラスがプライベートであるため、セッターを完全にファクトリーにロックします。コンシューマーはそれをダウンキャストしてセッターにアクセスできず、許可されるものを完全に制御できます。
public interface IFoo
{
int Foo{get;}
}
public class Factory
{
private class MyFoo : IFoo
{
public int Foo{get;set;}
}
public IFoo CreateFoo(int value) => new MyFoo{Foo = value};
}
それ以外は、プライベートメンバーにアクセスできる制御された環境でサードパーティのインターフェイスを実装するのに役立ちます。
たとえば、他のオブジェクトへのインターフェイスのインスタンスを提供するが、メインクラスにそれを実装させたくない場合は、内部クラスに実装させることができます。
public class Outer
{
private int _example;
private class Inner : ISomeInterface
{
Outer _outer;
public Inner(Outer outer){_outer = outer;}
public int DoStuff() => _outer._example;
}
public void DoStuff(){_someDependency.DoBar(new Inner(this)); }
}
通常、ネストされたクラスNは、CがCの外部で(直接)使用してはならない内部で何かを使用する必要があるとき、および何らかの理由で何かが既存のオブジェクトではなく新しいタイプのオブジェクトである必要があるという理由で、クラスCの内部に作成されますタイプ。
これは、ほとんどの場合、インターフェイスを実装するオブジェクトを返すメソッドを実装するときに発生します。そのオブジェクトの具体的なタイプは、他の場所では役に立たないため、非表示にしたいと考えています。
IEnumerableの実装は、この良い例です。
_class BlobOfBusinessData: IEnumerable<BusinessDatum>
{
public IEnumerator<BusinessDatum> GetEnumerator()
{
return new BusinessDatumEnumerator(...);
}
class BusinessDatumEnumerator: IEnumerator<BusinessDatum>
{
...
}
}
_
BlobOfBusinessData
の外部の誰かが具体的なBusinessDatumEnumerator
型を知ったり気にしたりする理由はないので、BlobOfBusinessData
の内部に保持することもできます。
これは、IEnumerable
を適切に実装する方法の「ベストプラクティス」の例ではなく、アイデアを理解するための最低限のものであるため、明示的なIEnumerable.GetEnumerator()
のようなものは省略しました方法。
では、なぜネストされたクラスを作成するのでしょうか。
私はいくつかの重要な理由を考えることができます:
多くの場合、ネストされたクラスはクラスの実装の詳細です。メインクラスのユーザーは自分の存在を気にする必要はありません。メインクラスのユーザーがコードを変更しなくても、自由に変更できるはずです。
タイプ、変数、関数などをスコープ内で適切でない限り、スコープ内で追加しないでください。これはカプセル化とは少し異なります。入れ子になった型のインターフェイスを公開すると便利な場合がありますが、入れ子になった型の適切な場所は依然としてメインクラスです。 C++ランドでは、イテレータ型がその一例です。 C#の経験が少ないので、具体的な例を示すことはできません。
ネストされたクラスをメインクラスと同じスコープに移動することが名前の汚染である理由を説明する単純な例を作成してみましょう。リンクリストクラスを実装しているとしましょう。通常は、
publid class LinkedList
{
class Node { ... }
// Use Node to implement the LinkedList class.
}
Node
をLinkedList
と同じスコープに移動する場合は、
public class LinkedListNode
{
}
public class LinkedList
{
// Use LinkedListNode to implement the class
}
LinkedListNode
は、LinkedList
クラス自体なしでは役に立ちません。 LinkedList
がLinkedListNode
のユーザーが使用できるLinkedList
オブジェクトを返すいくつかの関数を提供したとしても、LinkedListNode
はLinkedList
使用されている。そのため、「ノード」クラスをLinkedList
のピアクラスにすると、包含スコープが汚染されます。
関連するヘルパークラスには、ネストされたパブリッククラスを使用しています。
public class MyRecord {
// stuff
public class Comparer : IComparer<MyRecord> {
}
public class EqualsComparer : IEqualsComparer<MyRecord> {
}
}
MyRecord[] array;
Arrays.sort(array, new MyRecord.Comparer());
関連するバリエーションに使用します。
// Class that may or may not be mutable.
public class MyRecord {
protected string name;
public virtual String Name { get => name; set => throw new InvalidOperation(); }
public Mutable {
public override String { get => name; set => name = value; }
}
}
MyRecord mutableRecord = new MyRecord.Mutable();
呼び出し側は、どのバージョンがどの問題に適しているかを選択できます。場合によっては、クラスを1つのパスで完全に構築できず、変更可能なバージョンが必要になることがあります。これは、循環データを処理する場合は常に当てはまります。ミュータブルは後で読み取り専用に変換できます。
私はそれらを内部記録に使用します
public class MyClass {
List<Line> lines = new List<Line>();
public void AddUser( string name, string address ) => lines.Add(new Line { Name = name, Address = address });
class Line { string Name; string Address; }
}
ネストされたクラスは、クラスのインスタンスを複数作成する場合、またはそのタイプをさらに利用可能にする場合に使用できます。
ネストされたクラスは、カプセル化を増加させるだけでなく、コードをより読みやすく保守しやすくします。