web-dev-qa-db-ja.com

Enumのリストを使用することは良い習慣ですか?

私は現在、ユーザーがいて、各ユーザーが1つまたは複数の役割を持つシステムで作業しています。ユーザーに列挙値のリストを使用することは良い習慣ですか?私はこれ以上何も考えられませんが、これは問題を感じません。

enum Role{
  Admin = 1,
  User = 2,
}

class User{
   ...
   List<Role> Roles {get;set;}
}
32
Dexie

TL; DR:enumのコレクションを使用することは、悪い設計につながることが多いため、通常はお勧めできません。 enumのコレクションは通常、特定のロジックを持つ個別のシステムエンティティを必要とします。

列挙型のいくつかの使用例を区別する必要があります。このリストは私の頭の上にあるだけなので、もっとケースがあるかもしれません...

例はすべてC#で記述されています。選択した言語にも同様の構成要素があるか、または自分で実装することもできます。

1。単一の値のみが有効です

この場合、値は排他的です。

public enum WorkStates
{
    Init,
    Pending,
    Done
}

PendingDoneの両方の作業を行うことは無効です。したがって、これらの値の1つだけが有効です。これは列挙型の良いユースケースです。

2。値の組み合わせは有効です
このケースはフラグとも呼ばれ、C#は[Flags]これらを使用するための列挙型属性。アイデアは、それぞれが1つの列挙型メンバーに対応するboolsまたはbitsのセットとしてモデル化できます。各メンバーは2の累乗の値を持つ必要があります。組み合わせは、ビットごとの演算子を使用して作成できます。

[Flags]
public enum Flags
{
    None = 0,
    Flag0 = 1, // 0x01, 1 << 0
    Flag1 = 2, // 0x02, 1 << 1
    Flag2 = 4, // 0x04, 1 << 2
    Flag3 = 8, // 0x08, 1 << 3
    Flag4 = 16, // 0x10, 1 << 4

    AFrequentlyUsedMask = Flag1 | Flag2 | Flag4,
    All = ~0 // bitwise negation of zero is all ones
}

列挙型メンバーのコレクションを使用することは、各列挙型メンバーが設定または設定解除された1ビットのみを表すため、このような場合にはやりすぎです。ほとんどの言語はこのような構成をサポートしていると思います。それ以外の場合は作成できます(例:bool[](1 << (int)YourEnum.SomeMember) - 1)。

a)すべての組み合わせが有効です

一部の単純なケースではこれらで問題ありませんが、タイプに基づいて追加の情報や動作が必要になることが多いため、オブジェクトのコレクションの方が適切な場合があります。

[Flags]
public enum Flavors
{
    Strawberry = 1,
    Vanilla = 2,
    Chocolate = 4
}

public class IceCream
{
    private Flavors _scoopFlavors;

    public IceCream(Flavors scoopFlavors)
    {
        _scoopFlavors = scoopFlavors
    }

    public bool HasFlavor(Flavors flavor)
    {
        return _scoopFlavors.HasFlag(flavor);
    }
}

(注:これは、アイスクリームのフレーバーのみを本当に気にすることを前提としています-アイスクリームをスクープとコーンのコレクションとしてモデル化する必要はありません)

b)一部の値の組み合わせは有効で、一部は無効です

これはよくあるシナリオです。場合によっては、2つの異なるものを1つの列挙型に入れることがよくあります。例:

[Flags]
public enum Parts
{
    Wheel = 1,
    Window = 2,
    Door = 4,
}

public class Building
{
    public Parts parts { get; set; }
}

public class Vehicle
{
    public Parts parts { get; set; }
}

ここで、VehicleBuildingの両方がDoorsとWindowsを持つことは完全に有効ですが、BuildingsがWheelsを持つことはあまり一般的ではありません。

この場合、ケース#1または#2a)のいずれかを実現するために、列挙型を部分に分割するか、オブジェクト階層を変更するか、あるいはその両方を行うことをお勧めします。

設計上の考慮事項

エンティティのタイプは、通常、列挙型によって提供される情報に類似していると見なすことができるため、どういうわけか、列挙型はOOの駆動要素ではない傾向があります。

たとえば#2のIceCreamサンプルでは、​​IceCreamエンティティはフラグの代わりにScoopオブジェクトのコレクションを持っています。

それほど純粋ではないアプローチは、ScoopFlavorプロパティを持つことです。純粋主義的なアプローチは、ScoopVanillaScoopChocolateScoop、...クラスの抽象基本クラスにすることです。

一番下の行はそれです:
1。 「何かのタイプ」であるすべてが列挙型である必要はありません
2。一部のシナリオで一部の列挙型メンバーが有効なフラグではない場合は、列挙型を複数の異なる列挙型に分割することを検討してください。

今あなたの例のために(少し変更されました):

public enum Role
{
    User,
    Admin
}

public class User
{
    public List<Role> Roles { get; set; }
}

この正確なケースは次のようにモデル化する必要があると思います(注:本当に拡張可能ではありません!):

public class User
{
    public bool IsAdmin { get; set; }
}

言い換えると、UserUserであることは暗黙であり、追加情報は彼がAdminであるかどうかです。

排他的ではない複数のロールを取得する場合(たとえば、UserAdminModeratorVIP、...にすることができます)、フラグ列挙型または抽象基本クラスまたはインターフェイスを使用するのに適しています。

クラスを使用してRoleを表すと、Roleが特定のアクションを実行できるかどうかを決定する責任を負うことができるため、責任の分離が向上します。

列挙型では、すべてのロールのロジックを1か所に配置する必要があります。これはOOの目的を無効にし、あなたを命令に戻します。

Moderatorに編集権限があり、Adminに編集権限と削除権限があるとします。

列挙型アプローチ(役割と権限を混在させないためにPermissionsと呼ばれます):

[Flags]
public enum Permissions
{
    None = 0
    CanEdit = 1,
    CanDelete = 2,

    ModeratorPermissions = CanEdit,
    AdminPermissions = ModeratorPermissions | CanDelete
}

public class User
{
    private Permissions _permissions;

    public bool CanExecute(IAction action)
    {
        if (action.Type == ActionType.Edit && _permissions.HasFlag(Permissions.CanEdit))
        {
            return true;
        }

        if (action.Type == ActionType.Delete && _permissions.HasFlag(Permissions.CanDelete))
        {
            return true;
        }

        return false;
    }
}

クラスアプローチ(これは完璧にはほど遠い、理想的にはビジターパターンでIActionが必要ですが、この投稿はすでに巨大です...):

public interface IRole
{
    bool CanExecute(IAction action);
}

public class ModeratorRole : IRole
{
    public virtual bool CanExecute(IAction action)
    {
         return action.Type == ActionType.Edit;
    }
}

public class AdminRole : ModeratorRole
{
     public override bool CanExecute(IAction action)
     {
         return base.CanExecute(action) || action.Type == ActionType.Delete;
     }
}

public class User
{
    private List<IRole> _roles;

    public bool CanExecute(IAction action)
    {
        _roles.Any(x => x.CanExecute(action));
    }
}

ただし、列挙型の使用は許容できるアプローチです(パフォーマンスなど)。ここでの決定は、モデル化されたシステムの要件によって異なります。

38

セットを使用しないのはなぜですか?リストを使用する場合:

  1. 同じロールを2回追加するのは簡単です
  2. リストの単純比較はここでは正しく機能しません:[ユーザー、管理者]は[管理者、ユーザー]と同じではありません
  3. 交差やマージなどの複雑な操作は簡単に実装できません

パフォーマンスが気になる場合は、たとえばJavaで EnumSet があり、ブール値の固定長配列として実装されています(i番目のブール要素は、i 'かどうかの質問に答えます) th Enum値がこのセットに存在するかどうか)。例えば、 EnumSet<Role>。また、 EnumMap も参照してください。 C#にも同様の機能があると思います。

78
gudok

ユーザーに列挙値のリストを使用することは良い習慣ですか?


短い答え:はい


より良い答え:はい、列挙型はドメイン内で何かを定義します。


設計時の回答:ドメイン自体に関してドメインをモデル化するクラス、構造などを作成して使用します。


コーディング時間の回答:列挙型をコードスリングする方法は次のとおりです...


推定された質問

  • 文字列を使用する必要がありますか?

    • 回答:いいえ。
  • 他のクラスを使用する必要がありますか?

    • さらに、はい。代わりに、いいえ。
  • Role列挙リストはUserで使用するのに十分ですか?

    • 何も思いつきません。それは証拠にない他の設計の詳細に大きく依存しています。

WTFボブ?

  • これは、プログラミング言語の技術に組み込まれた設計上の質問です。

    • enumは、モデルで「役割」を定義するための良い方法です。したがって、Listの役割は良いことです。
  • enumは文字列よりもはるかに優れています。

    • Roleは、ドメインに存在するすべてのロールの宣言です
    • 文字列「Admin」は、"A""d""m""i""n"という文字がこの順序で含まれている文字列です。ドメインに関する限り、nothingです。
  • Roleの概念にいくつかの命を与えるために設計する可能性のあるクラス-機能性-は、Role列挙型をうまく利用できます。

    • たとえば、あらゆる種類のロールのサブクラスではなく、このクラスインスタンスがどのようなロールかを示すRoleプロパティを用意します。
    • User.Rolesリストは、インスタンス化されたロールオブジェクトを必要としない場合、または必要としない場合に適しています。
    • そして、ロールをインスタンス化する必要がある場合、列挙型は、それをRoleFactoryクラスに伝達する明確でタイプセーフな方法です。そして、取るに足らないことではないが、コードリーダーにとっては。
4
radarbob

それらを組み合わせることができるように列挙型を記述します。基数2の指数関数を使用すると、それらをすべて1つの列挙型に結合し、1つのプロパティを持ち、それらをチェックできます。あなたの列挙型はこれをリリーにする必要があります

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
4
Rémi

それは私が見たものではありませんが、それが機能しない理由はわかりません。ただし、並べ替えられたリストを使用して、だまされないようにすることができます。

より一般的なアプローチは、シングルユーザーレベルです。 system、admin、power user、userなど。システムはすべてを実行でき、ほとんどのことを管理できます。

ロールの値を2の累乗として設定する場合、ロールをintとして保存し、それらを本当に1つのフィールドに保存したいが、それがすべての人の好みではない場合は、ロールを派生させることができます。

だからあなたは持っているかもしれません:

Back office role 1
Front office role 2
Warehouse role 4
Systems role 8

したがって、ユーザーの役割の値が6の場合、フロントオフィスと倉庫の役割が割り当てられます。

3
Robbie Dee

HashSet を使用すると、重複を防ぎ、比較方法が増えます。

そうでなければEnumをうまく使う

メソッドBoolean IsInRole(Role R)を追加します

そして、私はセットをスキップします

List<Role> roles = new List<Role>();
Public List<Role> Roles { get {return roles;} }
2
paparazzo

あなたが与える単純化した例は結構ですが、通常それよりも複雑です。たとえば、ユーザーとロールをデータベースに永続化する場合は、列挙型を使用するのではなく、データベーステーブルにロールを定義する必要があります。これにより、参照整合性が提供されます。

1
Mohair

システム(ソフトウェア、オペレーティングシステム、組織など)が役割と権限を扱う場合、役割を追加してそれらの権限を管理することが役立つ場合があります。
これをソースコードを変更してアーカイブできる場合は、列挙型を使用しても問題ありません。しかし、ある時点で、システムのユーザーは役割と権限を管理できるようになりたいと考えています。列挙型は使用できなくなります。
代わりに、構成可能なデータ構造が必要になります。つまり、役割に割り当てられた一連の権限も保持するUserRoleクラス。
Userクラスには、一連のロールがあります。ユーザーの権限を確認する必要がある場合は、すべてのロール権限の和集合が作成され、問題の権限が含まれているかどうかが確認されます。

0
vikingosegundo