Apakah praktik yang baik untuk menggunakan Daftar Enum?

32

Saat ini saya sedang mengerjakan sistem di mana ada Pengguna, dan setiap pengguna memiliki satu atau beberapa peran. Apakah praktik yang baik untuk menggunakan nilai Daftar Enum pada Pengguna? Saya tidak bisa memikirkan sesuatu yang lebih baik, tetapi ini tidak terasa baik-baik saja.

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

class User{
   ...
   List<Role> Roles {get;set;}
}
Dexie
sumber
6
Terlihat baik untuk saya, saya akan tertarik untuk melihat komentar orang lain yang bertentangan.
David Scholefield
9
@ MatthewRock itu generalisasi yang cukup luas. Daftar <T> cukup umum di dunia .NET.
Graham
7
@MatthewRock .NET List adalah daftar array, yang memiliki properti yang sama untuk algoritma yang Anda sebutkan.
sangat bingung
18
@ MatthewRock - tidak, Anda berbicara tentang daftar TERKAIT, ketika pertanyaannya, dan semua orang, berbicara tentang antarmuka Daftar generik.
Davor Ždralo
5
Properti otomatis adalah fitur khas C # (get; set; syntax). Juga penamaan kelas Daftar.
jaypb

Jawaban:

37

TL; DR: Biasanya merupakan ide yang buruk untuk menggunakan koleksi enum karena sering mengarah pada desain yang buruk. Kumpulan enum biasanya membutuhkan entitas sistem yang berbeda dengan logika spesifik.

Penting untuk membedakan antara beberapa kasus penggunaan enum. Daftar ini hanya dari atas kepala saya sehingga mungkin ada lebih banyak kasus ...

Contoh-contohnya semuanya dalam C #, saya kira bahasa pilihan Anda akan memiliki konstruksi yang sama atau mungkin bagi Anda untuk menerapkannya sendiri.

1. Hanya nilai tunggal yang valid

Dalam hal ini, nilainya eksklusif, misalnya

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Tidak valid memiliki beberapa pekerjaan yang keduanya Pendingdan Done. Karenanya hanya satu dari nilai-nilai ini yang valid. Ini adalah kasus penggunaan enum yang baik.

2. Kombinasi nilai valid.
Kasus ini juga disebut flags, C # menyediakan [Flags]atribut enum untuk bekerja dengannya. Idenya dapat dimodelkan sebagai satu set bools atau bits dengan masing-masing sesuai dengan satu anggota enum. Setiap anggota harus memiliki nilai kekuatan dua. Kombinasi dapat dibuat menggunakan operator bitwise:

[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
}

Menggunakan koleksi anggota enum adalah pembunuhan berlebihan dalam kasus seperti itu karena setiap anggota enum hanya mewakili satu bit yang ditetapkan atau tidak disetel. Saya kira sebagian besar bahasa mendukung konstruksi seperti ini. Kalau tidak, Anda dapat membuatnya (misalnya menggunakan bool[]dan mengatasinya dengan (1 << (int)YourEnum.SomeMember) - 1).

a) Semua kombinasi valid

Meskipun ini ok dalam beberapa kasus sederhana, kumpulan objek mungkin lebih tepat karena Anda sering memerlukan informasi atau perilaku tambahan berdasarkan jenisnya.

[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);
    }
}

(catatan: ini mengasumsikan Anda hanya benar-benar peduli dengan rasa es krim - bahwa Anda tidak perlu memodelkan es krim sebagai koleksi sendok dan kerucut)

b) Beberapa kombinasi nilai valid dan beberapa tidak

Ini adalah skenario yang sering terjadi. Yang mungkin terjadi adalah Anda memasukkan dua hal yang berbeda ke dalam satu enum. Contoh:

[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; }
}

Sekarang walaupun benar-benar valid untuk keduanya Vehicledan Buildingmemiliki Doors dan Windows, tidak biasa bagi Buildings untuk memiliki Wheels.

Dalam hal ini, akan lebih baik untuk memecah enum menjadi beberapa bagian dan / atau memodifikasi hirarki objek untuk mencapai kedua kasus # 1 atau # 2a).

Pertimbangan desain

Entah bagaimana, enum cenderung bukan elemen penggerak dalam OO karena jenis entitas dapat dianggap mirip dengan informasi yang biasanya disediakan oleh enum.

Ambil contoh IceCreamsampel dari # 2, IceCreamentitas bukannya bendera memiliki koleksi Scoopobjek.

Pendekatan yang kurang murni adalah untuk Scoopmemiliki Flavorproperti. Pendekatan murni akan untuk Scoopmenjadi kelas dasar abstrak untuk VanillaScoop, ChocolateScoop, ... kelas sebagai gantinya.

Intinya adalah bahwa:
1. Tidak semua yang merupakan "tipe sesuatu" harus enum
2. Ketika beberapa anggota enum bukan bendera yang valid dalam beberapa skenario, pertimbangkan untuk membagi enum menjadi beberapa enum yang berbeda.

Sekarang untuk contoh Anda (sedikit berubah):

public enum Role
{
    User,
    Admin
}

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

Saya pikir case yang tepat ini harus dimodelkan sebagai (note: not really extensible!):

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

Dengan kata lain - itu adalah implisit, bahwa itu Useradalah User, info tambahan adalah apakah dia seorang Admin.

Jika Anda bisa memiliki peran ganda yang tidak eksklusif (misalnya Userbisa Admin, Moderator, VIP, ... pada saat yang sama), yang akan menjadi saat yang tepat untuk menggunakan bendera enum atau kelas dasar ABSTRAK atau interface.

Menggunakan kelas untuk mewakili Rolepetunjuk mengarah ke pemisahan tanggung jawab yang lebih baik di mana a Roledapat memiliki tanggung jawab untuk memutuskan apakah ia dapat melakukan tindakan yang diberikan.

Dengan enum Anda harus memiliki logika di satu tempat untuk semua peran. Yang mengalahkan tujuan OO dan membawa Anda kembali ke keharusan.

Bayangkan bahwa seorang Moderatormemiliki hak edit dan Adminmemiliki hak mengedit dan menghapus.

Pendekatan Enum (dipanggil Permissionsagar tidak mencampuradukkan peran dan izin):

[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;
    }
}

Pendekatan kelas (ini jauh dari sempurna, idealnya, Anda menginginkan IActionpola pengunjung dalam tetapi postingan ini sudah sangat besar ...):

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));
    }
}

Menggunakan enum mungkin merupakan pendekatan yang dapat diterima (misalnya kinerja). Keputusan di sini tergantung pada persyaratan sistem yang dimodelkan.

Zdeněk Jelínek
sumber
3
Saya tidak berpikir bahwa [Flags]menyiratkan semua kombinasi valid lebih dari int menyiratkan keempat miliar nilai jenis itu valid. Ini hanya berarti mereka dapat digabungkan dalam satu bidang - pembatasan apa pun pada kombinasi dimiliki oleh logika tingkat tinggi.
Acak832
Peringatan: saat menggunakan [Flags], Anda harus mengatur nilai ke kekuatan dua, atau jika tidak, itu tidak akan berfungsi seperti yang diharapkan.
Arturo Torres Sánchez
@ Random832 Saya tidak pernah punya niat untuk mengatakan itu tetapi saya mengedit jawabannya - harap sekarang lebih jelas.
Zdeněk Jelínek
1
@ ArturoTorresSánchez Terima kasih atas masukannya, saya telah memperbaiki jawabannya dan juga menambahkan catatan penjelasan tentang itu.
Zdeněk Jelínek
Penjelasan hebat! Sebenarnya flags cocok dengan persyaratan sistem dengan sangat baik, namun akan lebih mudah menggunakan HashSets karena implementasi layanan yang ada.
Dexie
79

Mengapa tidak menggunakan Set? Jika menggunakan Daftar:

  1. Sangat mudah untuk menambahkan peran yang sama dua kali
  2. Perbandingan daftar naif tidak akan berfungsi dengan baik di sini: [Pengguna, Admin] tidak sama dengan [Admin, Pengguna]
  3. Operasi kompleks seperti perpotongan dan penggabungan tidak mudah untuk diterapkan

Jika Anda khawatir tentang kinerja, maka, misalnya di Jawa, ada EnumSetyang diimplementasikan sebagai array panjang tetap boolean (elemen boolean ke-i menjawab pertanyaan apakah nilai Enum ke-i ada di set ini atau tidak). Sebagai contoh EnumSet<Role>,. Juga lihat EnumMap. Saya menduga bahwa C # memiliki sesuatu yang serupa.

gudok
sumber
35
Sebenarnya di Java EnumSetdiimplementasikan sebagai bidang bit.
biziclop
4
Pertanyaan SO terkait pada HashSet<MyEnum>flags enums: stackoverflow.com/q/9077487/87698
Heinzi
3
.NET memiliki FlagsAttribute, yang memungkinkan Anda menggunakan operator bitwise untuk menyelesaikan enum. Kedengarannya mirip dengan Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute EDIT: Seharusnya saya melihat satu jawaban! ini memiliki contoh yang bagus tentang ini.
ps2goat
4

Tulis enum Anda sehingga Anda bisa menggabungkannya. Dengan menggunakan basis 2 eksponensial, Anda dapat menggabungkan semuanya dalam satu enum, memiliki 1 properti, dan dapat memeriksanya. Enum Anda harus lile ini

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
Resi
sumber
3
Ada alasan mengapa Anda tidak menggunakan 1?
Robbie Dee
1
@RobbieDee tidak efektif saya bisa menggunakan 1, 2, 4, 8, dll. Anda benar
Rémi
5
Nah, jika Anda menggunakan Java, sudah ada EnumSetyang mengimplementasikan ini untuk enums. Anda memiliki semua aritmatika boolean yang sudah diabstraksikan, ditambah beberapa metode lain untuk membuatnya lebih mudah digunakan, ditambah memori yang kecil dan kinerja yang baik.
fr13d
2
@ fr13d Saya ac # guy dan karena pertanyaannya tidak memiliki tag java Saya pikir jawaban ini berlaku lebih banyak
Rémi
1
Benar, @Rémi. C # menyediakan [flags]atribut, yang dieksplorasi oleh @ Zdeněk Jelínek di posnya .
fr13d
4

Apakah praktik yang baik untuk menggunakan nilai Daftar Enum pada Pengguna?


Jawaban singkat : Ya


Jawaban singkat yang lebih baik : Ya, enum mendefinisikan sesuatu di domain.


Jawaban waktu-desain : Buat dan gunakan kelas, struktur, dll. Yang memodelkan domain dari segi domain itu sendiri.


Jawaban waktu pengkodean : Inilah cara sling kode sling ...


Pertanyaan-pertanyaan yang disimpulkan :

  • Haruskah saya menggunakan string?

    • jawaban: Tidak.
  • Haruskah saya menggunakan kelas lain?

    • Selain itu, ya. Alih-alih, tidak.
  • Apakah Roledaftar enum cukup untuk digunakan User?

    • Saya tidak punya ide. Ini sangat tergantung pada detail desain lain yang tidak ada dalam bukti.

WTF Bob?

  • Ini adalah pertanyaan desain yang dibingkai dalam teknis bahasa pemrograman.

    • An enumadalah cara yang baik untuk mendefinisikan "Peran" dalam model Anda. Maka Listperan adalah hal yang baik.
  • enum jauh lebih unggul dari string.

    • Role adalah deklarasi SEMUA peran yang ada di domain.
    • String "Admin" adalah string dengan huruf "A" "d" "m" "i" "n", dalam urutan itu. Ini tidak ada artinya sejauh menyangkut domain.
  • Setiap kelas yang mungkin Anda rancang untuk memberikan konsep Rolekehidupan - fungsionalitas - dapat memanfaatkan Roleenum dengan baik.

    • Misalnya daripada sub-kelas untuk setiap jenis peran, miliki a Role properti yang memberi tahu peran seperti apa instance kelas ini.
    • SEBUAH User.Roles daftar wajar ketika kita tidak perlu, atau tidak ingin, benda peran instantiated.
    • Dan ketika kita perlu membuat instance peran, enum adalah cara aman yang tidak ambigu untuk mengomunikasikannya ke RoleFactorykelas; dan, tidak signifikan, kepada pembaca kode.
radarbob
sumber
3

Itu bukan sesuatu yang saya lihat tetapi saya tidak melihat alasan mengapa itu tidak berhasil. Anda mungkin ingin menggunakan daftar yang diurutkan sehingga bertahan terhadap dupes.

Pendekatan yang lebih umum adalah tingkat pengguna tunggal misalnya sistem, admin, pengguna listrik, pengguna dll. Sistem dapat melakukan segalanya, admin sebagian besar hal dan seterusnya.

Jika Anda menetapkan nilai peran sebagai kekuatan dua, Anda bisa menyimpan peran sebagai int dan mendapatkan peran jika Anda benar-benar ingin menyimpannya dalam satu bidang tetapi itu mungkin tidak sesuai selera semua orang.

Jadi, Anda mungkin memiliki:

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

Jadi jika pengguna memiliki nilai peran 6 maka mereka akan memiliki peran Front office dan Warehouse.

Robbie Dee
sumber
2

Gunakan HashSet karena mencegah duplikat dan memiliki lebih banyak metode perbandingan

Jika tidak, gunakan Enum dengan baik

Tambahkan metode Boolean IsInRole (Peran R)

dan saya akan melewatkan set

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

Contoh sederhana yang Anda berikan baik-baik saja, tetapi biasanya lebih rumit dari itu. Misalnya, jika Anda akan bertahan pengguna dan peran dalam database, maka Anda harus mendefinisikan peran dalam tabel database daripada menggunakan enum. Itu memberi Anda integritas referensial.

Mohair
sumber
0

Jika suatu sistem - mungkin itu perangkat lunak, sistem operasi atau organisasi - berurusan dengan peran dan izin, akan tiba saatnya berguna untuk menambahkan peran dan mengelola izin untuknya.
Jika ini dapat diarsipkan dengan mengubah kode sumber, mungkin ok, untuk tetap menggunakan enum. Tetapi pada titik tertentu pengguna sistem ingin dapat mengelola peran dan izin. Dari enum menjadi tidak dapat digunakan.
Sebagai gantinya Anda akan ingin memiliki struktur data yang dapat dikonfigurasi. yaitu kelas UserRole yang juga memegang set izin yang ditugaskan untuk peran tersebut.
Kelas pengguna akan memiliki seperangkat peran. Jika ada kebutuhan untuk memeriksa izin pengguna, sekumpulan izin peran semua dibuat dan diperiksa jika itu berisi izin yang dipermasalahkan.

VikingoS berkata Reinstate Monica
sumber