Ao trabalhar em aplicativos, muitas vezes você precisará representar um grupo de constantes na lógica de negócios e até mesmo nas camadas de domínio. No entanto, você deve evitar usar tipos de enumeração, ou enums, na camada de domínio e, em vez disso, usar alternativas como tipos de registro.
Por que? Neste artigo, explicaremos as desvantagens do uso de enumerações na camada de domínio.
Crie um projeto de aplicativo de console no Visual Studio
Primeiro, vamos criar um projeto de aplicativo de console .NET Core no Visual Studio. Supondo que o Visual Studio 2022 esteja instalado em seu sistema, siga as etapas descritas abaixo para criar um novo projeto de aplicativo de console .NET Core.
- Inicie o IDE do Visual Studio.
- Clique em “Criar novo projeto”.
- Na janela “Criar novo projeto”, selecione “Aplicativo de console (.NET Core)” na lista de modelos exibida.
- Clique em Avançar.
- Na janela “Configure seu novo projeto”, especifique o nome e o local do novo projeto.
- Clique em Avançar.
- Na janela “Informações adicionais” mostrada a seguir, escolha “.NET 8.0 (Long Term Support)” como a versão do framework que você gostaria de usar.
- Clique em Criar.
Usaremos esse projeto de aplicativo de console .NET 8 para trabalhar com os exemplos de código mostrados nas seções subsequentes deste artigo.
O que há de errado com enumerações?
Embora os tipos de enumeração possam fornecer flexibilidade na manutenção de um conjunto de valores constantes em seu aplicativo, eles geralmente introduzem um acoplamento forte entre o modelo de domínio e o código que o utiliza, limitando assim sua capacidade de evoluir o modelo de domínio.
Considere a seguinte enumeração usada para definir funções de usuário e administrador.
public enum Roles { User, Administrator, Reviewer, SuperAdmin }
Você também pode usar um enum para definir um grupo de constantes, conforme mostrado no trecho de código a seguir.
public enum Roles { User = 1, Administrator = 2, Reviewer = 3, SuperAdmin = 4 }
Problema 1: cheiros de encapsulamento
Agora, suponha que você precise saber se uma função específica está relacionada a uma função de administrador. O trecho de código a seguir ilustra um método de extensão chamado IsAdmin que verifica se uma função específica é Administrador ou SuperAdmin.
public static class RolesExtensions { public static bool IsAdmin(this Roles roles) => roles == Roles.Administrator || roles == Roles.SuperAdmin; }
Isso nos leva ao primeiro problema com o uso de enums na camada de domínio. Embora você possa operar no enum usando métodos de extensão, seu código quebra o princípio do encapsulamento porque a lógica para consultar o modelo e o modelo que você criou são separados. Em outras palavras, a lógica que verifica o modelo não está dentro da mesma classe. Este é um antipadrão, e um modelo desse tipo é frequentemente conhecido como modelo anêmico.
Problema 2: código espaguete
Outro problema com enums: muitas vezes você pode precisar usar conversões explícitas no código do seu aplicativo para recuperar um valor de uma enumeração. A linha de código a seguir ilustra isso.
int role = (int)Roles.User;
Casts explícitos não são uma boa abordagem. Eles são sempre caros em termos de desempenho e implicam que você usou tipos incompatíveis em seu aplicativo ou que os tipos não foram definidos corretamente. O uso de enums na camada de domínio pode levar ao uso de conversões explícitas em todo o aplicativo, sobrecarregando o código e dificultando a leitura e a manutenção.
Problema 3: Restrições de nomenclatura
Lembre-se de que você não pode incluir caracteres de espaço nos nomes de constantes de enumeração em C#. Portanto, o código a seguir não é válido em C#.
public enum Roles { Admin, Super Admin }
Você pode aproveitar os atributos para superar essa limitação.
using System.ComponentModel.DataAnnotations; public enum Roles { Admin, (Display(Name = "Super Admin")) SuperAdmin }
No entanto, você terá problemas quando seu aplicativo precisar fornecer suporte para localidades diferentes.
Use tipos de registro em vez de enumerações
Uma alternativa melhor é usar tipos de registro. Você pode aproveitar as vantagens dos tipos de registro para criar um tipo imutável, conforme mostrado no trecho de código fornecido abaixo.
public record Roles(int Id) { public static Roles User { get; } = new(1); public static Roles Administrator { get; } = new(2); public static Roles Reviewer { get; } = new(3); public static Roles SuperAdmin { get; } = new(4); }
Um objeto imutável é um objeto que, uma vez instanciado, não pode ser alterado. Conseqüentemente, os registros possuem segurança de thread intrínseca e imunidade às condições de corrida. Objetos imutáveis também tornam seu código mais legível e fácil de manter.
Um benefício significativo do uso de tipos de registro é preservar o encapsulamento porque qualquer método de extensão escrito pode fazer parte do próprio modelo. Lembre-se de que você não pode incluir nenhum método dentro de um enum.
Além disso, os registros facilitam o fornecimento de nomes significativos, como ilustra o código a seguir.
public record Roles(int Id, string Name) { public static Roles User { get; } = new(1, "User"); public static Roles Administrator { get; } = new(2, "Administrator"); public static Roles Reviewer { get; } = new(3, "Reviewer"); public static Roles SuperAdmin { get; } = new(4, "Super Admin"); public override string ToString() => Name; }
Agora você pode acessar as constantes do registro Roles da mesma maneira que acessa constantes de enumeração.
Roles admin = Roles.Administrator; Roles user = Roles.User; Roles reviewer = Roles.Reviewer; Roles superAdmin = Roles.SuperAdmin;
Ao invocar o método ToString(), o nome da constante será exibido na janela do console conforme mostrado na Figura 1.
Alternativamente, você poderia usar uma classe em vez de um tipo de registro e então definir as constantes necessárias. No entanto, eu sempre preferiria um tipo de registro por motivos de desempenho. Os tipos de registro são leves e são muito mais rápidos que as classes. Um registro é em si um tipo de referência, mas usa sua própria verificação de igualdade integrada, que verifica por valor e não por referência.