Considere o exemplo abaixo. Qualquer alteração na enumeração ColorChoice afeta todas as subclasses IWindowColor.
As enums tendem a causar interfaces quebradiças? Existe algo melhor que um enum para permitir mais flexibilidade polimórfica?
enum class ColorChoice
{
Blue = 0,
Red = 1
};
class IWindowColor
{
public:
ColorChoice getColor() const=0;
void setColor( const ColorChoice value )=0;
};
Edit: desculpe por usar cores como meu exemplo, não é disso que se trata. Aqui está um exemplo diferente que evita o arenque vermelho e fornece mais informações sobre o que quero dizer com flexibilidade.
enum class CharacterType
{
orc = 0,
elf = 1
};
class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
CharacterType getCharacterType() const;
void setCharacterType( const CharacterType value );
};
Além disso, imagine que os identificadores da subclasse ISomethingThatNeedsToKnowWhatTypeOfCharacter apropriado sejam distribuídos por um padrão de design de fábrica. Agora eu tenho uma API que não pode ser estendida no futuro para um aplicativo diferente em que os tipos de caracteres permitidos sejam {human, dwarf}.
Edit: Apenas para ser mais concreto sobre o que estou trabalhando. Estou projetando uma forte ligação a essa especificação ( MusicXML ) e estou usando classes enum para representar os tipos na especificação que são declarados com xs: enumeração. Estou tentando pensar no que acontece quando a próxima versão (4.0) for lançada. Minha biblioteca de classes poderia funcionar no modo 3.0 e no modo 4.0? Se a próxima versão for 100% compatível com versões anteriores, talvez seja. Mas se os valores de enumeração foram removidos da especificação, eu estou morto na água.
fonte
Respostas:
Quando usadas corretamente, as enumerações são muito mais legíveis e robustas do que os "números mágicos" que substituem. Normalmente, não os vejo tornando o código mais frágil. Por exemplo:
value
é ou não um valor de cor válido. O compilador já fez isso.enum class
recurso em C ++ moderno ainda permite que você force as pessoas a escrever sempre o primeiro em vez do último.No entanto, o uso de uma enumeração para cores é questionável, porque em muitas (a maioria?) Situações não há razão para limitar o usuário a um conjunto tão pequeno de cores; é melhor deixá-los passar valores RGB arbitrários. Nos projetos com os quais trabalho, uma pequena lista de cores como essa só surgiria como parte de um conjunto de "temas" ou "estilos" que deveriam atuar como uma fina abstração sobre cores concretas.
Não sei ao certo qual é a sua pergunta sobre "flexibilidade polimórfica". As enums não possuem código executável; portanto, não há nada a fazer polimórfico. Talvez você esteja procurando pelo padrão de comando ?
Edit: Pós-edição, ainda não estou claro sobre que tipo de extensibilidade você está procurando, mas ainda acho que o padrão de comando é a coisa mais próxima que você pode obter de um "enum polimórfico".
fonte
Não, não faz. Existem dois casos: os implementadores irão
armazenar, retornar e encaminhar valores de enumeração, nunca operando neles, caso em que não são afetados por alterações na enumeração, ou
operar em valores de enum individuais; nesse caso, qualquer alteração no enum deve, naturalmente, naturalmente, inevitavelmente, necessariamente , ser contabilizada com uma alteração correspondente na lógica do implementador.
Se você colocar "Esfera", "Retângulo" e "Pirâmide" em uma enumeração "Shape" e passar essa enumeração para alguma
drawSolid()
função que você escreveu para desenhar o sólido correspondente, e então uma manhã você decide adicionar uma " Ikositetrahedron "para o enum, você não pode esperar que adrawSolid()
função permaneça inalterada; Se você esperava que, de alguma forma, desenhasse icositetrahedrons sem precisar primeiro escrever o código real para desenhar icositetrahedrons, a culpa é sua, não a culpa da enum. Então:Não, eles não. O que causa interfaces quebradiças são os programadores que se consideram ninjas, tentando compilar seu código sem ter avisos suficientes ativados. Em seguida, o compilador não avisa que sua
drawSolid()
função contém umaswitch
instrução que está faltando umacase
cláusula para o valor de enumeração "Ikositetrahedron" recém-adicionado.A maneira como ele deve funcionar é análoga à adição de um novo método virtual puro a uma classe base: você precisa implementar esse método em todos os herdeiros, caso contrário, o projeto não cria, nem deve construir.
Agora, verdade seja dita, enums não são uma construção orientada a objetos. Eles são mais um compromisso pragmático entre o paradigma orientado a objetos e o paradigma de programação estruturada.
A maneira pura e orientada a objetos de fazer as coisas não é ter enumerações, e sim objetos a todo momento.
Portanto, a maneira puramente orientada a objetos de implementar o exemplo com os sólidos é, obviamente, usar o polimorfismo: em vez de ter um único método monstruoso centralizado que sabe desenhar tudo e que precisa saber qual sólido desenhar, você declara " Classe "sólida" com um
draw()
método abstrato (virtual puro) e, em seguida, você adiciona as subclasses "Esfera", "Retângulo" e "Pirâmide", cada uma com sua própria implementação,draw()
que sabe como se desenhar.Dessa forma, quando você introduzir a subclasse "Ikositetrahedron", você precisará fornecer uma
draw()
função para ela, e o compilador o lembrará de fazer isso, não permitindo que você instale o "Icositetrahedron" caso contrário.fonte
default:
cláusula o façam. Uma pesquisa rápida sobre o assunto não produziu resultados mais definitivos; portanto, isso pode ser adequado como o assunto de outraprogrammers SE
pergunta.Pyramid
realmente saiba como fazerdraw()
uma pirâmide. Na melhor das hipóteses, pode derivarSolid
e ter umaGetTriangles()
método, e você pode passá-lo para umSolidDrawer
serviço. Eu pensei que estávamos nos afastando dos exemplos de objetos físicos como exemplos de objetos no POO.Enums não criam interfaces quebradiças. O uso indevido de enums faz.
Para que servem enums?
As enums são projetadas para serem usadas como conjuntos de constantes nomeadas de forma significativa. Eles devem ser usados quando:
Bons usos de enums:
System.DayOfWeek
) A menos que você esteja lidando com um calendário incrivelmente obscuro, haverá apenas 7 dias da semana.System.ConsoleColor
) Alguns podem discordar disso, mas o .Net optou por fazer isso por um motivo. No sistema de console da .Net, existem apenas 16 cores disponíveis para uso pelo console. Essas 16 cores correspondem à paleta de cores herdada conhecida como CGA ou 'Color Graphics Adapter' . Nenhum novo valor será adicionado, o que significa que essa é de fato uma aplicação razoável de um enum.Thread.State
) Os designers de Java decidiram que no modelo de encadeamento Java apenas haverá um conjunto fixo de estados nos quais aThread
pode estar e, para simplificar, esses diferentes estados são representados como enumerações . Isso significa que muitas verificações baseadas em estado são simples ifs e switches que, na prática, operam com valores inteiros, sem que o programador precise se preocupar com o que os valores realmente são.System.Text.RegularExpressions.RegexOptions
) Os bitflags são um uso muito comum de enumerações. Tão comum, de fato, que em .Net todas as enumerações têm umHasFlag(Enum flag)
método incorporado. Elas também suportam os operadores bit a bit e há umaFlagsAttribute
necessário marcar uma enumeração como destinada a ser usada como um conjunto de bitflags. Usando uma enum como um conjunto de sinalizadores, você pode representar um grupo de valores booleanos em um único valor, além de ter os sinalizadores claramente nomeados por conveniência. Isso seria extremamente benéfico para representar os sinalizadores de um registro de status em um emulador ou representar as permissões de um arquivo (leitura, gravação, execução) ou praticamente qualquer situação em que um conjunto de opções relacionadas não seja mutuamente exclusivo.Maus usos de enums:
True
bandeira implicaFalse
). Esse comportamento pode ser ainda mais suportado pelo uso de funções especializadas (ieIsTrue(flags)
eIsFalse(flags)
).fonte
RegexOptions
Msdn.microsoft.com/en-us/library/As enums são uma grande melhoria em relação aos números de identificação mágica para conjuntos fechados de valores que não possuem muitas funcionalidades associadas a eles. Geralmente você não se importa com o número realmente associado ao enum; Nesse caso, é fácil estender adicionando novas entradas no final, sem fragilidade.
O problema é quando você possui uma funcionalidade significativa associada à enumeração. Ou seja, você tem esse tipo de código:
Um ou dois deles está ok, mas assim que você tem esse tipo de enum significativo, essas
switch
declarações tendem a se multiplicar como tribbles. Agora, toda vez que você estende a enumeração, precisa caçar todas asswitch
es associadas para garantir que cobriu tudo. Isso é quebradiço. Fontes como o Clean Code sugerem que você tenha umswitch
por enum, no máximo.Nesse caso, o que você deve fazer é usar os princípios de OO e criar uma interface para esse tipo. Você ainda pode manter a enumeração para comunicar esse tipo, mas assim que precisar fazer alguma coisa, cria um objeto associado à enumeração, possivelmente usando um Factory. Isso é muito mais fácil de manter, pois você só precisa procurar um local para atualizar: sua Fábrica e adicionando uma nova classe.
fonte
Se você for cuidadoso em como usá-los, eu não consideraria as enums prejudiciais. Mas há algumas coisas a considerar se você deseja usá-las em algum código da biblioteca, em oposição a um único aplicativo.
Nunca remova ou reordene valores. Se em algum momento você tiver um valor enum em sua lista, esse valor deverá ser associado a esse nome por toda a eternidade. Se você quiser, em algum momento poderá renomear valores para o que
deprecated_orc
quer que seja, mas, ao não removê-los, você poderá manter a compatibilidade com mais facilidade com o código antigo compilado com o conjunto antigo de enumerações. Se algum código novo não puder lidar com constantes enum antigas, gere um erro adequado lá ou verifique se esse valor não atinge esse trecho de código.Não faça aritmética neles. Em particular, não faça comparações de pedidos. Por quê? Porque então você pode encontrar uma situação em que não pode manter os valores existentes e preservar uma ordenação são ao mesmo tempo. Tomemos, por exemplo, um enum para as direções da bússola: N = 0, NE = 1, E = 2, SE = 3,… Agora, se após alguma atualização você incluir NNE e assim por diante entre as direções existentes, você poderá adicioná-las ao final da lista e, portanto, quebre a ordem ou você as intercale com as chaves existentes e, assim, quebre os mapeamentos estabelecidos usados no código legado. Ou você descontinua todas as chaves antigas e possui um conjunto de chaves completamente novo, junto com algum código de compatibilidade que se traduz entre chaves antigas e novas por causa do código legado.
Escolha um tamanho suficiente. Por padrão, o compilador usará o menor número inteiro que pode conter todos os valores de enumeração. O que significa que se, em alguma atualização, seu conjunto de enumerações possíveis se expandir de 254 para 259, você precisará repentinamente de 2 bytes para cada valor de enumeração, em vez de um. Isso pode danificar os layouts de estrutura e classe em todo o lugar, portanto, evite isso usando um tamanho suficiente no primeiro design. O C ++ 11 oferece muito controle aqui, mas especificar uma entrada também
LAST_SPECIES_VALUE=65535
deve ajudar.Tenha um registro central. Como você deseja corrigir o mapeamento entre nomes e valores, é ruim se você permitir que usuários de seu código adicionem novas constantes. Nenhum projeto usando seu código deve ter permissão para alterar esse cabeçalho para adicionar novos mapeamentos. Em vez disso, eles devem incomodá-lo para adicioná-los. Isso significa que, para o exemplo humano e anão que você mencionou, as enums são de fato um ajuste inadequado. Lá, seria melhor ter algum tipo de registro em tempo de execução, em que os usuários do seu código possam inserir uma string e recuperar um número único, agradavelmente envolvido em algum tipo opaco. O "número" pode até ser um ponteiro para a string em questão, não importa.
Apesar do fato de que o meu último ponto acima faz da enums um ajuste inadequado para sua situação hipotética, sua situação real em que algumas especificações podem mudar e você teria que atualizar algum código parece se encaixar muito bem com um registro central da sua biblioteca. Portanto, as enums devem ser apropriadas se você levar minhas outras sugestões a sério.
fonte