Programação, Scala

Case classes em Scala

Uma das idéias que fundamentam a linguagem Scala é a de “limpar” o código e permitir grande expressividade com poucas linhas de programação. Esta é uma característica normalmente reservada às linguagens dinâmicas como Ruby, Perl e Python. No entanto, diferente dessas, Scala é uma linguagem de propósito geral que pode ser utilizada tanto em formato script quanto em código binário/compilado. Desta forma é uma das linguagems compiladas mais expressivas, e o poder das case classes, que discutiremos aqui, soma-se ao grande arsenal de recursos sintáticos que a linguagem tem a nos oferecer.

Motivação para as case classes
Durante o desenvolvimento de sistemas repetimos frequentemente o mesmo trecho de código, muitas vezes por imposição da linguagem. Por exemplo, todas as classes instanciáveis devem ter algum campo de dados, de outra forma não necessitariamos de uma classe e poderiamos implementar apenas métodos estáticos.

Vejamos como é a implementação de uma classe em Scala usando um idioma de Java:
class Automovel(val modelo: String) {
def getModelo: String = modelo
def ==(a2: Automovel): Boolean = {
modelo.equals(a2.modelo)
}
}

Observe que o parâmetro modelo foi definido como um val. Desta forma o compilador Scala define esta variável como um dado de instância da classe Automovel. Não houvesse a definição como val, modelo seria visível apenas ao construtor, no momento em que o objeto fosse instanciado. Temos apenas um método getter, getModelo, visto que val é um valor imutável e não pode ser alterado após a instanciação. Para modificá-lo, seria preciso instanciar outro objeto Automovel.

Caso tivéssemos 10 a 15 classes, número facilmente encontrado em sistemas relativamente simples, todas as classes teriam repetições de algum trecho de código semelhante ao descrito acima – construtores que fazem a mesma coisa, getters para membros da classe, setters para atualizações, e assim por diante.

Este sintoma é particularmente observado na linguagem Java, onde o excesso de “burocracia” programática estende os programas desnecessariamente, gerando incontáveis repetições dos mesmos padrões de código como try,catch e finally, definições de POJOs(getters, setters, e assim por diante). Um pequeno sistema Java pode consistir em dezenas de POJOs todos com código praticamente idêntico exceto nomes de tabelas e diferentes campos de dados.

Case classes
Scala soluciona esse problema através de case classes. Case classes são “facilitadoras de sintaxe”: geram classes que possuem o comportamento básico esperado de todos os objetos sem a necessidade de repetir setters, getters, metodos de comparação e outros códigos de apoio. Também permitem o uso de tal classe em reconhecimento de padrões, ou pattern matching. Por fim, case classes criam um método apply padrão em um objeto complementar(“companion Object”) que pode ser usado para instanciar estáticamente novas instâncias da classe(padrão “Factory”).

Para definir uma classe completa, basta uma linha de código:
case class Automovel(modelo: String)

Esta classe tem todos os métodos básicos como equals e hashCode, um objeto complementar com método apply e ainda pode ser usada em expressões de reconhecimento de padrões(pattern matching). Tudo isso foi gerado pelo compilador sem que tivéssemos que redigir uma só linha adicional de código. Na definição da case class, deixamos apenas os dados que diferenciam esta classe de outras classes: os campos de dados, e podem ser definidos métodos específicos em seu corpo, caso este exista.

Vejamos um exemplo contextualizado:
case class Automovel(modelo: String)
object ScalaTutorialLiterati {
def main(args: Array[String]) = {
val auto1 = Automovel("Fusca")
val auto2 = Automovel("Fusca")
val auto3 = Automovel("Ferrari Testarossa")
if (auto1 == auto2) {
println("Auto1 e Auto2 são o mesmo carro.\n")
}
if (auto1 != auto3) {
println("Autos 1 e 3 são carros distintos.\n")
}
}
}

Todo o código repetitivo da classe Automovel foi removido, e a definição da classe foi resumida a uma linha apenas(destacada em negrito). A comparação entre dois objetos Automovel acontece de forma correta e a construção de objetos pode ser diretamente através do método Automovel("Modelo de Carro"), usando o padrão factory. Não é preciso criar um AutomovelFactory para construir instâncias de Automovel.

Ao código acima podemos adicionar a análise de casos com base na classe Automovel, assim:

auto1 match {
case Automovel("Fusca") => { println("Encontrado um Automovel Fusca.\n") }
case Automovel(outros) => { println("Automovel: " + outros + "\n") }
case _ => { println("auto1 não parece ser um Automovel\n") }
}

A análise de casos aliada à orientação a objetos torna a linguagem Scala extremamente poderosa por unir idiomas tipicamente encontrados em linguagens funcionais a práticas de OOP há muito estabelecidas em C++ e Java. Note que no reconhecimento de padrões acima listado, o campo modelo do Automovel é examinado caso a caso. No primeiro caso identifica-se apenas um automóvel de modelo específico “Fusca”, no segundo caso identificamos qualquer outro tipo de Automovel, e o valor de modelo encontrado, caso seja encontrado, é atribuído a outros. No último caso não foi identificado um objeto do tipo Automovel, e o sistema trata o caso usando o “coringa” ou “wildcard” _ que bate com todos os valores possíveis.

Conclusão
Case classes em Scala permitem construir código mais limpo, sem repetições de trechos desnecessários. As case classes estabelecem o comportamento padrão dos objetos, adicionando métodos de comparação, hashCode, e a capacidade de tornar a classe comparável através de pattern matching.

Standard