Scala: os cuidados com encapsulamento
Um dos pontos difíceis de lidar em qualquer sistema está ligado com quebra de encapsulamento. Em Java, uma vez que o padrão de uma variável membro permite que ela seja acessada por fora do objeto, protegemos os dados através do modificador private
e um getter:
public class Conta { private List<Movimentacao> movimentacoes = new ArrayList<Movimentacao>(); private double saldo;
public double getSaldo() { return saldo; }
public List<Movimentacao> getMovimentacoes() { return movimentacoes; } }
Enquanto a criação de getters é um padrão amplamente adotado, muitas vezes ainda deixamos vazar os objetos mutáveis, como no caso da lista acima. Para não vazar referências a tais objetos, quebrando o encapsulamento e possívelmente alguma funcionalidade interna, retornamos uma cópia segura da mesma:
public List<Movimentacao> getMovimentacoes() { // protegendo a lista interna para não quebrar o encapsulamento return Collections.unmodifiableList(movimentacoes); }
class Conta def movimentacoes @movimentacoes.dup end end
Em Scala, existem diversas variações para declaração de variáveis membro. A padrão que ainda nos ajuda a criar good citizens, é a definição de uma variável privada, final:
class Conta(saldo:Double)
A desvantagem é que as vezes é interessante colocar um getter para expor o conteúdo referenciado. No caso de uma conta bancária, é interessante expor o saldo da mesmo, mas não uma lista de movimentações sem protegê-la.
Para isso, podemos definir uma val
(equivalente a variáveis final em Java), uma vez que o papel do getter é gerado automaticamente pelo compilador (nomes de variáveis e métodos estão no mesmo contexto, portanto não requerem recompilação quando um substitui o outro):
class Conta(val saldo:Double)
A desvantagem pode estar em desejar alterar o saldo da conta e não poder. Nesse caso definiríamos o membro como var
:
class Conta(var saldo:Double)
A desvantagem nessa abordagem é a criação do getter e do setter de exposição: ao definir uma variável como "private" mas permitir o acesso a mesma, o encapsulamento é novamente quebrado. Portanto, para membros cuja referência pode ser alterada (vars), é interessante gerar o "getter" mas não expor o setter para não quebrar o encapsulamento, como em:
class Conta(private var \_saldo:Double) { def saldo = \_saldo }
Mas nesse caso, o código deixa de ser curto como nas abordagens onde o encapsulamento é mais facilmente quebrado. O mesmo é válido para muitas linguagens: é mais fácil quebrar o encapsulamento dos objetos do que executar a troca de mensagens para efetuar uma transferência:
class Conta(private var \_saldo:Double) { def saldo = \_saldo def debita(valor:Double) { movimentacoes.add(new Movimentacao(-valor)); \_saldo -= valor } }
O encapsulamento é a base de orientação a objetos. Nos momentos em que é adequado trabalhar de forma OO, é ele que permite a troca de mensagens entre objetos prevenindo o usuário de depender de como o tipo funciona internamente. Das maneiras demonstradas aqui, escolha a maneira de encapsular suficientemente adequada para faciltiar a manutenção de suas classes a longo prazo (para evitar quebras inesperadas).
Como pergunta, será que seria interessante a linguagem fornecer uma opcao só de getter para var?