現在、ユーザーが存在し、各ユーザーが1つまたは複数のロールを持つシステムで作業しています。ユーザーに列挙値のリストを使用することは良い習慣ですか?これ以上良いものは考えられませんが、これは大丈夫ではありません。
enum Role{
Admin = 1,
User = 2,
}
class User{
...
List<Role> Roles {get;set;}
}
現在、ユーザーが存在し、各ユーザーが1つまたは複数のロールを持つシステムで作業しています。ユーザーに列挙値のリストを使用することは良い習慣ですか?これ以上良いものは考えられませんが、これは大丈夫ではありません。
enum Role{
Admin = 1,
User = 2,
}
class User{
...
List<Role> Roles {get;set;}
}
回答:
TL; DR:多くの場合、不適切なデザインにつながるため、列挙型のコレクションを使用することは悪い考えです。通常、列挙型のコレクションは、特定のロジックを持つ個別のシステムエンティティを呼び出します。
enumのいくつかのユースケースを区別する必要があります。このリストは私の頭の一番上にあるので、もっと多くのケースがあるかもしれません...
例はすべてC#で記述されています。選択する言語は同様の構成要素を持っているか、自分で実装できる可能性があります。
1.単一の値のみが有効です
この場合、値は排他的です。例えば
public enum WorkStates
{
Init,
Pending,
Done
}
両方のあるいくつかの作業を持って無効であるPending
としますDone
。したがって、これらの値の1つのみが有効です。これは、enumの適切な使用例です。
2.値の組み合わせは有効です。
この場合はフラグとも呼ばれ、C#は[Flags]
これらを操作する列挙属性を提供します。アイデアはのセットbool
またはbit
、それぞれが1つの列挙メンバーに対応する sの。各メンバーには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; }
}
それは両方のために完全に有効ですが今Vehicle
とBuilding
持っているDoor
sおよびWindow
Sをするために、それは非常に普通のではないBuilding
sが持っているWheel
のを。
この場合、#1または#2a)のいずれかのケースを達成するために、enumを複数の部分に分割し、オブジェクト階層を変更することをお勧めします。
設計上の考慮事項
どういうわけか、エンティティのタイプは、enumによって通常提供される情報に類似していると見なすことができるため、enumはOOの駆動要素ではない傾向があります。
たとえば、IceCream
#2からサンプルを取り、IceCream
エンティティはフラグの代わりにScoop
オブジェクトのコレクションを持ちます。
それほど純粋ではないアプローチはScoop
、Flavor
財産を持つことです。純粋主義のアプローチはScoop
、の抽象基底クラスになることですVanillaScoop
。ChocolateScoop
代わりに、...クラス。
要点は次のとおり
です。1.「何かのタイプ」であるすべてのものを列挙する必要はありません
はない2.いくつかの列挙型メンバーが有効なフラグではない場合、列挙型を複数の異なる列挙型に分割することを検討してください。
今、あなたの例のために(わずかに変更されました):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
この正確なケースは次のようにモデル化されるべきだと思います(注:実際には拡張可能ではありません!):
public class User
{
public bool IsAdmin { get; set; }
}
言い換えれば、それは暗黙的であり、であることUser
がUser
、追加情報は彼がAdmin
です。
あなたが排他的ではありません複数の役割を持って取得する場合(例えばUser
することができAdmin
、Moderator
、VIP
、...同時に)、それはどちらかのフラグが列挙型を使用するには良い時間やabtract基底クラスまたはインタフェースになります。
クラスを使用して表現することRole
によりRole
、特定のアクションを実行できるかどうかを決定する責任を持つことができる責任の分離が改善されます。
列挙型を使用すると、すべてのロールのロジックを1か所にまとめる必要があります。これはオブジェクト指向の目的を打ち負かし、あなたを命令に戻します。
に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));
}
}
ただし、列挙型を使用することは、許容可能なアプローチ(たとえば、パフォーマンス)である場合があります。ここでの決定は、モデル化されたシステムの要件に依存します。
[Flags]
intがそのタイプの40億の値がすべて有効であることを意味する以上に、すべての組み合わせが有効であることを意味するとは思わない。それは単に、単一のフィールド内でそれらを結合できることを意味します-組み合わせに対する制限は、より高いレベルのロジックに属します。
[Flags]
する場合、値を2のべき乗に設定する必要があります。そうしないと、期待どおりに動作しません。
Setを使用しないのはなぜですか?リストを使用する場合:
パフォーマンスが懸念される場合、たとえば、Javaでは、EnumSet
ブールの固定長配列として実装されます(i番目のブール要素は、このセットにEnum値が存在するかどうかの質問に答えます)。たとえば、EnumSet<Role>
。また、を参照してくださいEnumMap
。C#に似たものがあると思います。
EnumSet
ビットフィールドとして実装されます。
HashSet<MyEnum>
vsフラグの列挙に関連するSOの質問:stackoverflow.com/q/9077487/87698-Heinzi
FlagsAttribute
、ビットごとの演算子を使用して列挙型を連結できるようになっています。Javaに似た音EnumSet
。msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute編集:1つの答えを見下ろすべきでした!これの良い例があります。
それらを結合できるように列挙型を書きます。基数2の指数関数を使用すると、それらをすべて1つの列挙型に結合し、1つのプロパティを持ち、それらをチェックできます。あなたのenumはこれをlileする必要があります
enum MyEnum
{
FIRST_CHOICE = 2,
SECOND_CHOICE = 4,
THIRD_CHOICE = 8
}
EnumSet
、enum
sにこれを実装するJavaが既にあります。すべてのブール演算がすでに抽象化されており、さらに使いやすくするためのいくつかのメソッドに加えて、小さなメモリと優れたパフォーマンスがあります。
ユーザーに列挙値のリストを使用することは良い習慣ですか?
短い答え:はい
より良い短い答え:はい、enumはドメイン内の何かを定義します。
設計時の答え:ドメイン自体の観点からドメインをモデル化するクラス、構造などを作成して使用します。
コーディング時間の答え:列挙型をコードスリングする方法は次のとおりです...
推測される質問:
文字列を使用する必要がありますか?
他のクラスを使用する必要がありますか?
あるRole
で使用するのに十分な列挙型のリストはUser
?
WTFボブ?
これは、プログラミング言語の技術に組み込まれた設計上の質問です。
enum
モデル内の「役割」を定義するための良い方法です。だから、List
役割のは良いことです。enum
文字列よりもはるかに優れています。
Role
ドメインに存在するすべてのロールの宣言です。 "A"
"d"
"m"
"i"
"n"
がその順に並んだ文字列です。ドメインに関する限り、それは何でもありません。Role
何らかの生命の概念を提供するために設計する可能性のあるクラス(機能性)は、Role
列挙型をうまく活用できます。
Role
このクラスインスタンスがどのような種類のロールであるかを伝えるプロパティを用意します。User.Roles
我々が必要としない、または、インスタンス化されたロールオブジェクトをしたくないときリストが合理的です。RoleFactory
クラスに伝えるための明確なタイプセーフな方法です。そして、ささいなことではありませんが、コードリーダーに。これは私が見たものではありませんが、うまくいかない理由はありません。ただし、並べ替えられたリストを使用して、だまされないようにすることができます。
より一般的なアプローチは、システム、管理者、パワーユーザー、ユーザーなどの単一のユーザーレベルです。システムはすべてを実行でき、ほとんどのことを管理できます。
ロールの値を2のべき乗として設定する場合、ロールをintとして保存し、本当に単一のフィールドに保存したいが、それが誰の好みにも合わない場合は、ロールを導出できます。
だからあなたは持っているかもしれません:
Back office role 1
Front office role 2
Warehouse role 4
Systems role 8
したがって、ユーザーのロール値が6の場合、フロントオフィスとウェアハウスのロールがあります。
システム(ソフトウェア、オペレーションシステム、組織など)が役割と権限を扱う場合、役割を追加して権限を管理すると便利な場合があります。
ソースコードを変更することでこれをアーカイブできる場合は、列挙型に固執しても大丈夫かもしれません。しかし、ある時点で、システムのユーザーはロールと許可を管理できるようにしたいと考えています。列挙型が使用できなくなります。
代わりに、構成可能なデータ構造が必要になります。すなわち、ロールに割り当てられた許可セットも保持するUserRoleクラス。
Userクラスには一連のロールがあります。ユーザーの許可を確認する必要がある場合、すべての役割の許可の和集合が作成され、問題の許可が含まれているかどうかが確認されます。