Programação, Scala

Objetos e classes “complementares” em Scala

Na linguagem Scala temos o conceito de Object’s, os quais são equivalentes às classes de métodos estáticos de Java(a classe possui apenas métodos estáticos e um construtor privado, de modo que não pode ser instanciada). Objects de Scala não são instanciados durante a execução do programa: estes são instanciados uma única vez pela JVM durante a inicialização do programa, e apenas uma instância existirá para cada object.

Já as classes podem ser livremente instanciadas em tempo de execução. Quando desejamos ter um conjunto de métodos estáticos para uma certa estrutura de dados e, ao mesmo tempo, precisamos de alguma funcionalidade de instâncias, será preciso criar uma classe estática para as funções e outra classe para criarmos instâncias de um objeto.

Esta situação dá origem ao padrão Factory dos sistemas orientados a objetos. Os factories em Java, por exemplo, funcionam da seguinte forma: uma certa Classe representa uma estrutura de dados qualquer, e uma outra classe, normalmente chamada ClasseFactory(em relação à primeira classe) é responsável por criar, remover, alterar objetos do tipo Classe. Algo estilo:
class Automovel {
private String modelo;
public Automovel(String m) {
modelo = m;
}
public String getModelo() {
return modelo;
}
public void setModelo(String m) {
modelo = m;
}
}
class AutomovelFactory {
public static Automovel constroiAutomovel(String nome){
return new Automovel(nome);
}
// metodos para buscas de automoveis
// serialização a bancos de dados, etc
}

Em essência, o padrão Factory se resume a isso: uma classe é instanciada por outra através de métodos estáticos, os quais configuram e controlam a criação dos objetos de acordo com certas regras de negócio. Assim passamos a ter um único ponto para construção de objetos Automovel, e podemos controlar sua origem, otimizar sua construção, ou até mesmo impedir que mais que um certo número de automóveis exista. A analogia com uma fábrica real dá origem ao nome do padrão.

Scala aplica esta idéia e torna praticamente todo o código acima desnecessário. Em Scala, a mesma idéia seria implementada assim:

case class Automovel(val modelo: String)

Simplificou um pouco, não? Na verdade as case classes de Scala são um poderoso recurso da linguagem, o qual é capaz de limpar o código e deixar o compilador trabalhar para criar trechos repetitivos. Falamos sobre a importância das case classes em outro post de nosso tutorial Scala, aqui vamos ver apenas por que após criar uma case class podemos simplesmente chamar Automovel(“Fusca”) e termos uma instância de Automovel. A resposta está nos objetos complementares, ou “companion objects”.

O que são objetos complementares?
Objetos complementares são a contrapartida estática das classes e possuem o mesmo nome e pacote da classe correspondente. O padrão de Factory discutido logo acima é implementado em Scala utilizando um Object cujo nome é idêntico ao nome de uma classe e implementado no mesmo arquivo fonte desta classe. O object e a classe podem intercambiar dados privados e completar a funcionalidade um do outro sem violação dos princípios de encapsulamento e proteção de dados da metodologia OOP. De fato, como vimos no exemplo da case class, sequer precisamos saber que existe um outro objeto por trás da classe

De fato os objetos complementares são uma implementação elegante dos padrões Factory e Singleton. Singleton, porque apenas uma instância do objeto complementar pode existir. E Factory porque o objeto complementar é capaz de criar instâncias da classe correspondente e acessar seus membros privados.

Vejamos um exemplo. A case class Automovel que acabamos de ver é na verdade uma combinação de uma classe Automovel e um objeto complementar que como vimos também é chamado Automovel. Sua implementação é mais ou menos equivalente a:

class Automovel(val modelo: String) {
// case class também gera outros metodos
// não listados aqui...
}
object Automovel {
def apply(m: String) = new Automovel(m)
def procura(nome: String, automoveis: List[Automovel]) = {
// codigo para procurar um automovel em uma lista ...
}
}

Implementamos a classe e o object no mesmo arquivo fonte, de modo que o usuário de nosso código sequer precisa conhecer os detalhes da implementação. O usuário desta interface verá apenas uma classe Automovel que possui um factory com mesmo nome e outras funções estáticas, como uma busca por automóveis e, possivelmente código DAO para armazenamento e busca de objetos Automovel no banco de dados. A classe e seu objeto complementar serão carregados ao mesmo tempo quando o import correspondente for chamado.

Conclusão
Scala oferece incontáveis facilidades sintáticas para deixar o código fonte o mais limpo possível. Conhecendo as convenções de Scala, idiomas elegantes podem ser desenvolvidos, evitando repetir trechos redundantes e código que o compilador pode gerar automaticamente. As case classes são um exemplo do uso de objetos complementares para simplificar o código e permitir que métodos estáticos relativos a uma classe possam ser organizados em mesmo espaço de nomes de sua classe correspondente, sem exigir a criação de classes estáticas “Factory”. Código DAO e outras rotinas de gerenciamento de um conjunto de objetos de uma determianda classe pode ser implementado em seu objeto complementar, mantendo tudo relacionado a essa classe sob o mesmo nome de pacote e no mesmo arquivo fonte.

Standard