私はいくつかのプロジェクトの過程で、不変 (読み取り専用) オブジェクトと不変オブジェクト グラフを作成するためのパターンを開発しました。不変オブジェクトには、100% スレッドセーフであるという利点があるため、スレッド間で再利用できます。私の仕事では、このパターンを Web アプリケーションで、構成設定や、メモリにロードしてキャッシュするその他のオブジェクトによく使用します。キャッシュされたオブジェクトは、予期せず変更されないことを保証するために、常に不変である必要があります。
もちろん、次の例のように、不変オブジェクトを簡単に設計できます。
public class SampleElement
{
private Guid id;
private string name;
public SampleElement(Guid id, string name)
{
this.id = id;
this.name = name;
}
public Guid Id
{
get { return id; }
}
public string Name
{
get { return name; }
}
}
これは単純なクラスでは問題ありませんが、より複雑なクラスでは、コンストラクターを介してすべての値を渡すという概念は好きではありません。プロパティにセッターを設定することはより望ましく、新しいオブジェクトを作成するコードは読みやすくなります。
では、setter を使用して不変オブジェクトを作成するにはどうすればよいでしょうか。
さて、私のパターンでは、オブジェクトは最初から完全に変更可能であり、1 回のメソッド呼び出しでフリーズするまで続きます。オブジェクトが凍結されると、永久に不変のままになります。再び可変オブジェクトに変えることはできません。オブジェクトの変更可能なバージョンが必要な場合は、単純に複製します。
さて、いくつかのコードに進みます。次のコード スニペットでは、パターンを最も単純な形式に煮詰めようとしています。IElement は、すべての不変オブジェクトが最終的に実装する必要がある基本インターフェイスです。
public interface IElement : ICloneable
{
bool IsReadOnly { get; }
void MakeReadOnly();
}
Element クラスは、IElement インターフェイスのデフォルトの実装です。
public abstract class Element : IElement
{
private bool immutable;
public bool IsReadOnly
{
get { return immutable; }
}
public virtual void MakeReadOnly()
{
immutable = true;
}
protected virtual void FailIfImmutable()
{
if (immutable) throw new ImmutableElementException(this);
}
...
}
上記の SampleElement クラスをリファクタリングして、不変オブジェクト パターンを実装しましょう。
public class SampleElement : Element
{
private Guid id;
private string name;
public SampleElement() {}
public Guid Id
{
get
{
return id;
}
set
{
FailIfImmutable();
id = value;
}
}
public string Name
{
get
{
return name;
}
set
{
FailIfImmutable();
name = value;
}
}
}
MakeReadOnly() メソッドを呼び出してオブジェクトが不変としてマークされていない限り、Id プロパティと Name プロパティを変更できるようになりました。不変になると、setter を呼び出すと ImmutableElementException が発生します。
最後の注意: 完全なパターンは、ここに示すコード スニペットよりも複雑です。また、不変オブジェクトのコレクションと、不変オブジェクト グラフの完全なオブジェクト グラフのサポートも含まれています。完全なパターンでは、最も外側のオブジェクトで MakeReadOnly() メソッドを呼び出すことにより、オブジェクト グラフ全体を不変にすることができます。このパターンを使用してより大きなオブジェクト モデルの作成を開始すると、オブジェクトがリークするリスクが高まります。リーキー オブジェクトとは、オブジェクトに変更を加える前に FailIfImmutable() メソッドの呼び出しに失敗したオブジェクトです。リークをテストするために、単体テストで使用する一般的なリーク検出クラスも開発しました。リフレクションを使用して、すべてのプロパティとメソッドが ImmutableElementException を不変状態でスローするかどうかをテストします。つまり、TDD がここで使用されます。
私はこのパターンがとても好きになり、大きな利点を見つけました。ですから、私が知りたいのは、同様のパターンを使用している方がいるかどうかです。はいの場合、それを文書化した優れたリソースを知っていますか? 私は本質的に、潜在的な改善と、このトピックに関する既存の標準を探しています。