ReadOnly な参照型のメンバ変数

最近、プログラミング関係の事はほとんど書かないようになっていたのですが、ブログ継続の意味でも、差障りのない形でできるだけメモしていこうかと思います。さて、「ある参照型のメンバ変数を読み取り専用にしたい」と言う要求が出てきたのですが、ちょっとうまくいかなくて悩み中です。例えば、以下のような、あるデータを保持するためのクラスがあったとします。

// 各種操作が許可されているかどうかを表すクラス
public class Permission {
    public bool Printing { get; set; }
    public bool Assembly { get; set; }
    public bool ModifyContents { get; set; }
    public bool CopyContents { get; set; }
    public bool Accessibility { get; set; }
    // …(省略)…
}

// 暗号化するための情報を保持するクラス
public class Encryption {
    public string OwnerPassword { get; set; }
    public string UserPassword { get; set; }
    public int Method { get; set; }
    public Permission Permission { get; set; }
}

そして、この Encryption オブジェクトをメンバ変数に持つ Reader と Writer と言う 2 つのクラスがあるのですが、Reader クラスに関しては内容の参照のみで変更を許可したくないと言う要求が発生します。しかし、単に「get のみを public で定義する」と言う方法だと、Encryption オブジェクトの各メンバ変数の内容が変更される恐れがあります。

public class Reader {
    // …(省略)…
    public Encryption Encryption { get; private set; } // オブジェクトの内部状態も変更不可にしたい
}

public class Writer {
    // …(省略)…
    public Encryption Encryption { get; set; } // こっちは変更されても良い
}

この要求に応える方法として、ReadOnly なインターフェース (IReadOnlyEncryption) を定義し、Reader クラスの Encryption プロパティではそのインターフェースを介して情報を提供すると言うものがあります(参考:クラス内のオブジェクトの参照を返さないようにすること)。この方法は、メンバ変数が全て値型、または immutable な参照型であれば問題ないのですが、メンバ変数にも immutable ではない参照型が紛れている場合、難しくなります。

public interface IReadOnlyPermission { // …(省略)… }
public interface IReadOnlyEncryption {
    string OwnerPassword { get; }
    string UserPassword { get; }
    int Method { get; }
    
    // Permission Permission { get; } だとオブジェクトの内部状態が変更可能となってしまう。
    IReadOnlyPermission Permission { get; }
}

// Permission プロパティの戻り値の型がインターフェースと不整合になる
public class Encryption : IReadOnlyEncryption { /* …(省略)… */ }

整合性を取るためには Permission クラスを immutable にすると良い(コンストラクタで初期化後は値の変更はできない)のでしょうが、Permission クラスのコンストラクタが引数のお化け状態になってしまう(最大で 10 個程度の bool 値を指定しなければならない)のと、Permission で定義されている操作群中のある一つの操作の許可状態のみを変更したい場合でも再度コンストラクタで初期化しなければならなくなるので、ユーザコード側での扱いが煩雑になる可能性があります。

public class Encryption {
    public Encryption(ReadOnlyEncryption cp) { /* コピー */ }
    // …(省略)…
    public Permission { get; set; }
}

public class ReadOnlyEncryption {
    public ReadOnlyEncryption(Encryption cp) { /* コピー */ }
    // …(省略)…
    public IReadOnlyPermission { get; }
}

いっそ、上記のように継承関係のない Encryption クラスと ReadOnlyEncryption クラスの 2 つを用意して、コンストラクタで相互にコピー可能なようにしておくのが無難でしょうか。

広告を非表示にする