💯

Enumとオープン・クローズド原則

2022/11/06に公開

SOLID原則のひとつ、 Open Closed Principle(OCP、開放閉鎖原則) の話です。

実際のコーディング中に遭遇するわかりやすい例として、Enum(列挙型)のような変数があって、取りうる値によって処理を分岐するようなケースがあります。

注記

以下のコードはC#で書いていますが、C#を知らなくてもだいたい意図は伝わるかと思います。
そして実際にコンパイル通したわけじゃないので、誤記があるかもです。

ユーザー種別によって処理を分岐する例

ありがちなのは、たとえばユーザー種別を表すEnumがあって、

public enum UserType {
    // 管理者
    Admin,
    // 一般
    General
}

管理者だけ可能な操作があったりする。

public void ChangePassword(User user, string newPassword) {
    // 一般ユーザーの場合は例外を投げる
    if (this.UserType == UserType.General) {
        throw new NotAuthorizedException();
    }

    ...
}

上のような書き方をしてしまいがちですが、これはオープン・クローズドになっていません。

たとえば、あとからゲストという種別を追加しようと思った場合、このUserTypeを参照している箇所すべてで修正の必要が生じるため、修正に対して「閉じている」と言えないわけです。

public enum UserType {
    Admin,
    General,
    // 追加
    Guest
}
public void ChangePassword(User user, string newPassword) {
    // ※ゲストユーザーの場合の考慮を追加する必要がある
    if (this.UserType == UserType.General
        || this.UserType == UserType.Guest) {
        throw new NotAuthorizedException();
    }

    ...
}

!= UserType.Admin という判定にしておけば修正不要だったんじゃないかというつっこみがあるかもしれませんが、ポイントはそこじゃないです。
SuperAdmin みたいな、管理者権限を持つ種別が追加になれば同じことです。

Enum側に情報を持たせる

オープン・クローズドにするためには、Enumを参照するほうに判断させるのではなく、 Enum自身に判断のための情報を持たせる 必要があります。

C#の場合は、Enum自体に任意のプロパティを定義することはできませんが、たとえば拡張メソッドとして定義する方法があります。

public static class UserTypeExtensions {

    public static bool CanChangePassword(this UserType userType) {
        return userType == UserType.Admin;
    }
}

上記のような拡張メソッドを1か所にまとめて定義しておき、参照する側では個々のEnum値に対する情報を持たないようにします。

public void ChangePassword(User user, string newPassword) {
    if (!this.UserType.CanChangePassword()) {
        throw new NotAuthorizedException();
    }

    ...
}

別の方法としては、Enum型を使わずに UserType という定数クラスにしてしまうこともできます。
やりたいことは同じです。

public class UserType {
    // パスワード変更可否をプロパティとして保持
    public bool CanChangePassword { get; init; }
    
    // 定義済みのインスタンスとして表現する
    public static readonly UserType Admin = new UserType { CanChangePassword = true };
    public static readonly UserType General = new UserType();
    public static readonly UserType Guest = new UserType();
}

あるいはinterfaceを使ってポリモーフィズムとして表現するほうが自然なケースもありますが、ここでは割愛します。

以上

SOLID原則のなかでもOCPは、「言っていることはわかるけど実際どうしたらいいのかがわからない」と思われがちな原則だと思います。

列挙型の値によって処理を分岐するケースはコーディングで頻出しますし、OCPの適用どころとしてわかりやすいんじゃないかと思います。

Discussion