Se você está escrevendo um código que usa muitas estruturas de dados lindas e imutáveis, as classes de caso parecem ser uma dádiva de Deus, oferecendo a você todos os itens a seguir gratuitamente com apenas uma palavra-chave:
- Tudo imutável por padrão
- Getters definidos automaticamente
- Implementação decente de toString ()
- Compatível com equals () e hashCode ()
- Objeto complementar com método unapply () para correspondência
Mas quais são as desvantagens de definir uma estrutura de dados imutável como uma classe de caso?
Que restrições impõe à classe ou aos seus clientes?
Existem situações em que você deve preferir uma aula que não seja o caso?
scala
case-class
Graham Lea
fonte
fonte
Respostas:
Uma grande desvantagem: uma classe de caso não pode estender uma classe de caso. Essa é a restrição.
Outras vantagens que você perdeu, listadas para integridade: serialização / desserialização compatível, sem necessidade de usar a palavra-chave "nova" para criar.
Eu prefiro classes não case para objetos com estado mutável, estado privado ou nenhum estado (por exemplo, a maioria dos componentes singleton). Classes de caso para praticamente todo o resto.
fonte
Primeiro as partes boas:
Tudo imutável por padrão
Sim, e pode até ser substituído (usando
var
) se você precisarGetters definidos automaticamente
Possível em qualquer classe prefixando params com
val
toString()
Implementação decenteSim, muito útil, mas pode ser feito manualmente em qualquer aula, se necessário
Compatível
equals()
ehashCode()
Combinado com fácil correspondência de padrões, este é o principal motivo pelo qual as pessoas usam classes de caso
Objeto companheiro com
unapply()
método para correspondênciaTambém é possível fazer manualmente em qualquer classe usando extratores
Esta lista também deve incluir o método de cópia super-poderoso, uma das melhores coisas que virão para Scala 2.8
Então o ruim, há apenas um punhado de restrições reais com classes de caso:
Você não pode definir
apply
no objeto complementar usando a mesma assinatura do método gerado pelo compiladorNa prática, porém, isso raramente é um problema. Mudar o comportamento do método apply gerado é garantido para surpreender os usuários e deve ser fortemente desencorajado, a única justificativa para fazer isso é validar os parâmetros de entrada - uma tarefa melhor realizada no corpo do construtor principal (que também torna a validação disponível ao usar
copy
)Você não pode subclass
Verdade, embora ainda seja possível que uma classe de caso seja uma descendente. Um padrão comum é construir uma hierarquia de classes de características, usando classes de caso como os nós folha da árvore.
Também vale a pena observar o
sealed
modificador. Qualquer subclasse de uma característica com este modificador deve ser declarada no mesmo arquivo. Ao fazer a correspondência de padrões com instâncias do traço, o compilador pode avisá-lo se você não tiver verificado todas as subclasses concretas possíveis. Quando combinado com classes de caso, isso pode oferecer um nível muito alto de confiança em seu código se ele compilar sem aviso.Como uma subclasse de Produto, as classes de caso não podem ter mais de 22 parâmetros
Nenhuma solução alternativa real, exceto para parar de abusar de classes com tantos parâmetros :)
Além disso...
Uma outra restrição às vezes observada é que Scala não suporta (atualmente) parâmetros preguiçosos (como
lazy val
s, mas como parâmetros). A solução alternativa para isso é usar um parâmetro por nome e atribuí-lo a um val preguiçoso no construtor. Infelizmente, parâmetros por nome não se misturam com correspondência de padrões, o que evita que a técnica seja usada com classes de caso, pois quebra o extrator gerado pelo compilador.Isso é relevante se você deseja implementar estruturas de dados lazy altamente funcionais e, com sorte, será resolvido com a adição de parâmetros lazy em uma versão futura do Scala.
fonte
Acho que o princípio TDD se aplica aqui: não superdimensione. Quando você declara algo como um
case class
, está declarando muitas funcionalidades. Isso diminuirá a flexibilidade que você tem para mudar de classe no futuro.Por exemplo, a
case class
tem umequals
método sobre os parâmetros do construtor. Você pode não se importar com isso ao escrever sua classe pela primeira vez, mas, posteriormente, pode decidir que deseja que a igualdade ignore alguns desses parâmetros ou faça algo um pouco diferente. No entanto, o código do cliente pode ser escrito enquanto isso depende dacase class
igualdade.fonte
Martin Odersky nos dá um bom ponto de partida em seu curso Princípios de Programação Funcional em Scala (Aula 4.6 - Correspondência de Padrões) que podemos usar quando devemos escolher entre classe e classe de caso. O capítulo 7 de Scala por exemplo contém o mesmo exemplo.
Além disso, adicionar uma nova classe Prod não acarreta nenhuma alteração no código existente:
Em contraste, adicionar um novo método requer a modificação de todas as classes existentes.
O mesmo problema foi resolvido com classes de caso.
Adicionar um novo método é uma mudança local.
Adicionar uma nova classe Prod requer, potencialmente, alterar toda a correspondência de padrões.
Transcrição da videoleta 4.6 Correspondência de padrões
Lembre-se: devemos usar isso como um ponto de partida e não como o único critério.
fonte
Estou citando isso
Scala cookbook
porAlvin Alexander
capítulo 6:objects
.Essa é uma das muitas coisas que achei interessantes neste livro.
Para fornecer vários construtores para uma classe de caso, é importante saber o que a declaração da classe de caso realmente faz.
Se você olhar o código que o compilador Scala gera para o exemplo de classe de caso, verá que ele cria dois arquivos de saída, Person $ .class e Person.class. Se você desmontar Person $ .class com o comando javap, verá que ele contém um método apply, junto com muitos outros:
Você também pode desmontar Person.class para ver o que ele contém. Para uma classe simples como essa, contém 20 métodos adicionais; esse inchaço oculto é um dos motivos pelos quais alguns desenvolvedores não gostam de classes de casos.
fonte