C#でBuilderパターン(effective java)


コンストラクタの引数が多い

Studentクラスは名前と誕生日を必須の情報として持っていて、オプションでニックネームがついている。
こんなクラスを使いたいと思いました。

var taro = new Student("太郎", new DateTime(1990, 1, 1));
var sachiko = new Student("幸子", new DateTime(1990, 1, 1), "さっちゃん");

必須2個と任意1個の引数だからまだいいけど、増えてくるととても大変になります。
そして任意のパラメータ分のオーバーロードを書きたくない。

メソッドチェーンで書きたい

コンストラクタを呼ぶときにパラメータを順次設定するメソッドを呼び出したいですね。

var taro = Student.Builder.Instance
                .SetName("Taro")
                .SetBirth(new DateTime(1995, 4, 18))
                .SetNickname("タロ")
                .Build();

var sachiko = Student.Builder.Instance
                .SetName("幸子")
                .Build();  // 誕生日をセットしていないのでコンパイルエラーにしたい

SetNameとSetBirthは必須だがSetNicknameは呼ばなくてもbuildしたい。
これは以下のコードで実現できます。

public class Student
    {
        public string Name { get; }
        public DateTime Birth { get; }
        public string Nickname { get; }

        private Student(Builder builder)
        {
            Name = builder.Name;
            Birth = builder.Birth;
            Nickname = builder.Nickname;
        }

        public interface IHasName
        {
            IHasBirth SetName(string name);
        }

        public interface IHasBirth
        {
            Builder SetBirth(DateTime birth);
        }

        public sealed class Builder : IHasName, IHasBirth
        {
            internal string Name { get; private set; }
            internal DateTime Birth { get; private set; }
            internal string Nickname { get; private set; } = "";

            public static IHasName Instance
            {
                get { return new Builder(); }
            }

            private Builder() { }

            public IHasBirth SetName(string name)
            {
                Name = name;
                return this;
            }

            public Builder SetBirth(DateTime birth)
            {
                Birth = birth;
                return this;
            }

            public Builder SetNickname(string nickname)
            {
                Nickname = nickname;
                return this;
            }

            public Student Build()
            {
                return new Student(this);
            }
        }
    }

StudentとBuilderクラスのコンストラクタはprivateにしてあるので、Instanceを介さないとインスタンス化できません。
InstanceからはIHasNameが返るのでSetName以外は呼べません。
そしてSetNameからはSetBirthしか呼べません。
SetBirthからはSetNicknameもできるし設定せずにインスタンス化もできます。

そしてStudentインスタンスはreadonlyなフィールドだけを持つので、どこかで変更されることもありません。必要に応じてメソッドを追加していきましょう。

参考

Builderパターン(Effective Java)

なんぞこれ。コンストラクタパラメータが異常に大杉る・・・。バカなの?死ぬの?そういう場合はBuilderパターンを検討してみよう。