É uma boa prática usar a Lista de enums?

32

Atualmente, estou trabalhando em um sistema em que há usuários e cada usuário tem uma ou várias funções. É uma boa prática usar os valores da Lista de Enum no Usuário? Não consigo pensar em nada melhor, mas isso não parece certo.

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

class User{
   ...
   List<Role> Roles {get;set;}
}
Dexie
fonte
6
Parece bom para mim, eu estaria interessado em ver os comentários de outras pessoas ao contrário.
David Scholefield 21/01
9
@MatthewRock é uma generalização bastante abrangente. A lista <T> é bastante comum no mundo .NET.
Graham
7
O @MatthewRock .NET List é um arraylist, que possui as mesmas propriedades para os algoritmos mencionados.
Eu estou tão confuso
18
@ MatthewRock - não, você está falando sobre listas LINKED, quando a pergunta e todos os outros estão falando sobre a interface genérica da lista.
Davor Ždralo
5
As propriedades automáticas são um recurso distinto do C # (a sintaxe get; set; Também a nomeação da classe List.
Jaypb

Respostas:

37

TL; DR: Geralmente, é uma má idéia usar uma coleção de enumerações, pois geralmente leva a um design inadequado. Uma coleção de enums geralmente requer entidades distintas do sistema com lógica específica.

É necessário distinguir entre alguns casos de uso de enum. Esta lista é apenas do topo da minha cabeça, então pode haver mais casos ...

Os exemplos estão todos em C #, acho que sua linguagem de escolha terá construções semelhantes ou seria possível implementá-las você mesmo.

1. Apenas um valor único é válido

Nesse caso, os valores são exclusivos, por exemplo

public enum WorkStates
{
    Init,
    Pending,
    Done
}

É inválido ter algum trabalho que seja ambos Pendinge Done. Portanto, apenas um desses valores é válido. Este é um bom caso de uso de enum.

2. Uma combinação de valores é válida.
Este caso também é chamado de sinalizadores, o C # fornece o [Flags]atributo enum para trabalhar com eles. A ideia pode ser modelada como um conjunto de bools ou bits, cada um correspondendo a um membro da enumeração. Cada membro deve ter um valor de poder de dois. As combinações podem ser criadas usando operadores bit a bit:

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

O uso de uma coleção de membros da enumeração é um exagero, pois cada membro da enumeração representa apenas um bit que está definido ou não. Eu acho que a maioria dos idiomas suporta construções como esta. Caso contrário, você pode criar um (por exemplo, use bool[]e resolva-o com (1 << (int)YourEnum.SomeMember) - 1).

a) Todas as combinações são válidas

Embora isso esteja correto em alguns casos simples, uma coleção de objetos pode ser mais apropriada, pois muitas vezes você precisa de informações ou comportamentos adicionais com base no tipo.

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

(nota: isso pressupõe que você realmente se preocupa apenas com os sabores do sorvete - que não precisa modelar o sorvete como uma coleção de bolas e um cone)

b) Algumas combinações de valores são válidas e outras não

Este é um cenário frequente. O caso pode ser que você esteja colocando duas coisas diferentes em uma enumeração. Exemplo:

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

Agora, embora seja completamente válido para ambos Vehiclee Buildingtenha Doors e Windows, não é muito comum que Buildings tenha Wheels.

Nesse caso, seria melhor dividir o enum em partes e / ou modificar a hierarquia de objetos para alcançar o caso nº 1 ou nº 2a).

Considerações de design

De alguma forma, as enumerações tendem a não ser os elementos determinantes no OO, pois o tipo de uma entidade pode ser considerado semelhante às informações que geralmente são fornecidas por uma enumeração.

Pegue, por exemplo, a IceCreamamostra do item 2, a IceCreamentidade teria, em vez de sinalizadores, uma coleção de Scoopobjetos.

A abordagem menos purista seria a Scoopde ter uma Flavorpropriedade. A abordagem purista seria o Scoopde ser uma classe base abstrata para VanillaScoop, ChocolateScoop, ... aulas em seu lugar.

A linha inferior é que:
1. Nem tudo que é um "tipo de algo" precisa ser enum
2. Quando algum membro da enum não é um sinalizador válido em algum cenário, considere dividir a enum em várias enumerações distintas.

Agora, para o seu exemplo (ligeiramente alterado):

public enum Role
{
    User,
    Admin
}

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

Eu acho que esse caso exato deve ser modelado como (nota: não é realmente extensível!):

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

Em outras palavras - está implícito que Useré um User, a informação adicional é se ele é um Admin.

Se você começa a ter múltiplos papéis que não são exclusivos (por exemplo, Userpode ser Admin, Moderator, VIP, ... ao mesmo tempo), isso seria um bom momento para usar tanto bandeiras enum ou uma classe base abtract ou interface.

O uso de uma classe para representar uma Roleleva a uma melhor separação de responsabilidades, onde a pessoa Rolepode ter a responsabilidade de decidir se pode executar uma determinada ação.

Com uma enumeração, você precisa ter a lógica em um só lugar para todas as funções. O que derrota o objetivo do OO e o leva de volta ao imperativo.

Imagine que a Moderatorpossui direitos de edição e Adminpossui direitos de edição e exclusão.

Abordagem enum (chamada Permissionspara não misturar funções e permissões):

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

Abordagem de classe (isso está longe de ser perfeito, idealmente, você desejaria o IActionpadrão de visitante, mas este post já é enorme ...):

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

Usar um enum pode ser uma abordagem aceitável (por exemplo, desempenho). A decisão aqui depende dos requisitos do sistema modelado.

Zdeněk Jelínek
fonte
3
Não acho que isso [Flags]implique que todas as combinações sejam válidas, assim como um int implica que todos os quatro bilhões de valores desse tipo sejam válidos. Significa simplesmente que eles podem ser combinados em um único campo - quaisquer restrições às combinações pertencem à lógica de nível superior.
usar o seguinte comando
Aviso: ao usar [Flags], você deve definir os valores para potências de dois, caso contrário, não funcionará conforme o esperado.
Arturo Torres Sánchez
@ Random832 Eu nunca tive a intenção de dizer isso, mas editei a resposta - espero que esteja mais clara agora.
Zdeněk Jelínek
1
@ ArturoTorresSánchez Obrigado pela contribuição, eu corrigi a resposta e também adicionei uma nota explicativa sobre isso.
Zdeněk Jelínek
Ótima explicação! Na verdade, os sinalizadores atendem muito bem aos requisitos do sistema, no entanto, será mais fácil usar o HashSets devido à implementação de serviço existente.
Dexie
79

Por que não usar Set? Se estiver usando a Lista:

  1. É fácil adicionar a mesma função duas vezes
  2. A comparação ingênua de listas não funcionará corretamente aqui: [Usuário, Admin] não é o mesmo que [Admin, Usuário]
  3. Operações complexas como interseção e mesclagem não são fáceis de implementar

Se você está preocupado com desempenho, por exemplo, em Java, existe um EnumSetque é implementado como uma matriz de comprimento fixo de booleanos (o elemento booleano responde à pergunta se o valor Enum está presente neste conjunto ou não). Por exemplo EnumSet<Role>,. Veja também EnumMap. Eu suspeito que c # tem algo semelhante.

gudok
fonte
35
Na verdade, em Java EnumSets são implementados como campos de bits.
biziclop
4
SO pergunta relacionada no HashSet<MyEnum>bandeiras vs. enums: stackoverflow.com/q/9077487/87698
Heinzi
3
O .NET has FlagsAttribute, que permite usar operadores bit a bit para concatenar enumerações. Soa semelhante ao Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute EDIT: Eu deveria ter procurado uma resposta! tem um bom exemplo disso.
Ps2goat
4

Escreva seu enum para poder combiná-los. Usando o exponencial base 2, você pode combiná-los todos em uma enumeração, ter 1 propriedade e verificar por eles. Sua enumeração deve demorar um pouco

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
Rémi
fonte
3
Alguma razão para você não estar usando 1?
Robbie Dee
1
@RobbieDee não, efetivamente, eu poderia ter usado 1, 2, 4, 8, etc. você está certo
Rémi
5
Bem, se você usa Java, ele já possui o EnumSetque implementa isso para enums. Você tem toda a aritmética booleana já abstraída, além de outros métodos para facilitar o uso, além de pouca memória e bom desempenho.
fr13d
2
@ fr13d estou ac # cara e uma vez que a questão não tem etiquetas java Acho que esta resposta aplicar mais em geral
Rémi
1
Certo, @ Rémi. O C # fornece o [flags]atributo que @ Zdeněk Jelínek explora em seu post .
fr13d
4

É uma boa prática usar os valores da Lista de Enum no Usuário?


Resposta curta : Sim


Melhor resposta curta : Sim, o enum define algo no domínio.


Resposta em tempo de design : crie e use classes, estruturas etc. que modelam o domínio em termos do próprio domínio.


Resposta em tempo de codificação : veja como codificar enums de sling ...


As perguntas inferidas :

  • Devo usar strings?

    • resposta: Não.
  • Devo usar alguma outra classe?

    • Além de sim. Em vez de, não.
  • A Rolelista de enumerações é suficiente para uso User?

    • Eu não faço ideia. É altamente dependente de outros detalhes do projeto que não estão em evidência.

WTF Bob?

  • Essa é uma questão de design que está enquadrada nos aspectos técnicos da linguagem de programação.

    • Uma enumé uma boa maneira de definir "Funções" no seu modelo. Então, um Listdos papéis é uma coisa boa.
  • enum é muito superior às cordas.

    • Role é uma declaração de TODAS as funções existentes no domínio.
    • A sequência "Admin" é uma sequência com as letras "A" "d" "m" "i" "n", nessa ordem. Não é nada no que diz respeito ao domínio.
  • Quaisquer classes que você possa criar para fornecer o conceito de Rolevida - funcionalidade - podem fazer bom uso da Roleenumeração.

    • Por exemplo, em vez de subclasse para todo tipo de função, tenha uma Rolepropriedade que indique que tipo de função é essa instância de classe.
    • Uma User.Roleslista é razoável quando não precisamos ou não queremos objetos de função instanciados.
    • E quando precisamos instanciar uma função, a enumeração é uma maneira segura e inequívoca do tipo de comunicar isso a uma RoleFactoryclasse; e, de maneira não insignificante, para o leitor de código.
radarbob
fonte
3

Isso não é algo que eu já vi, mas não vejo razão para que não funcione. Você pode querer usar a lista classificada para que ela se defenda contra os enganadores.

Uma abordagem mais comum é um nível de usuário único, por exemplo, sistema, administrador, usuário avançado, usuário etc. O sistema pode fazer tudo, administrar a maioria das coisas e assim por diante.

Se você definir os valores das funções como potências de dois, poderá armazená-la como int e derivá-las se realmente quiser armazená-las em um único campo, mas isso pode não ser do gosto de todos.

Então você pode ter:

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

Portanto, se um usuário tivesse um valor de função 6, ele teria as funções Front office e Warehouse.

Robbie Dee
fonte
2

Use o HashSet , pois evita duplicatas e possui mais métodos de comparação

Caso contrário, o bom uso do Enum

Adicionar um método Boolean IsInRole (Role R)

e eu pularia o set

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

O exemplo simplista que você dá é bom, mas geralmente é mais complicado que isso. Por exemplo, se você deseja persistir os usuários e funções em um banco de dados, defina as funções em uma tabela de banco de dados em vez de usar enumerações. Isso lhe dá integridade referencial.

Mohair
fonte
0

Se um sistema - seja um software, um sistema operacional ou uma organização - lida com funções e permissões, chega um momento em que é útil adicionar funções e gerenciar permissões para elas.
Se isso puder ser arquivado alterando o código-fonte, pode ser bom ficar com enumerações. Mas, em algum momento, o usuário do sistema deseja gerenciar funções e permissões. Do que enums tornam-se inutilizáveis.
Em vez disso, você desejará ter uma estrutura de dados configurável. ou seja, uma classe UserRole que também possui um conjunto de permissões atribuídas à função.
Uma classe de usuário teria um conjunto de funções. Se houver a necessidade de verificar a permissão do usuário, um conjunto de união de todas as permissões de funções é criado e verificado se contém a permissão em questão.

VikingoS diz Restabelecer Monica
fonte