Encapsulamento
O paradigma Orientação a Objetos (OO) se destaca por oferecer mecanismos que vão além da simples utilização de métodos, como ocorre em linguagens baseadas em estruturas simples, como as structs em C. Um desses mecanismos fundamentais é o encapsulamento.
O encapsulamento possibilita que uma classe oculte detalhes de sua implementação interna, expondo apenas o que é necessário para seu uso. Esse conceito ajuda a proteger os dados e a reduzir o acoplamento entre diferentes partes do software, promovendo um design mais modular e flexível.
Para implementar o encapsulamento, as linguagens de programação oferecem modificadores de acesso ou modificadores de visibilidade, geralmente representados por palavras-chave como public
, protected
e private
. Esses modificadores controlam quais partes de uma classe podem ser acessadas por outras classes ou objetos.
Em linguagens como Java e TypeScript, se nenhum modificador for definido explicitamente pelos desenvolvedores, o atributo ou método será definido como
public
por padrão.
Quando o modificador de um método é public
, isso implica que este método será visível e acionável em qualquer classe do sistema. No caso dos atributos, além de lidos diretamente, esses atributos também poderão ter seu valor alterado por qualquer outra classe da aplicação. O efeito prático é bastante similar ao que se tinha com as variáveis de uma struct em C. No entanto, essa abordagem não é adequada a boas práticas de projeto (design), já que o valor de um atributo pode ser alterado de forma incorreta durante a execução do programa, podendo levar a falhas com difícil detecção.
A seguir, há um exemplo que demonstra essa problemática:
class Aluno {
nome: string;
nota1: number;
nota2: number;
media: number;
constructor(nome: string, nota1: number, nota2: number) {
this.nome = nome;
this.nota1 = nota1;
this.nota2 = nota2;
this.media = (nota1+nota2) / 2;
}
}
let joao = new Aluno("João da Silva", 7, 10);
joao.nota1 = 10;
console.log("Média:", joao.media);
O modificador private
, por sua vez, restringe o acesso e escrita, no caso dos atributos, apenas para métodos da própria classe, privando as eventuais subclasses de seu acesso.
class Pessoa {
private _nome: string;
constructor(nome: string){
this._nome = nome;
}
}
let joao:Pessoa = new Pessoa("João");
console.log(joao.nome); // não reconhece
Para tanto, podemos controlar a exposição dos atributos com o uso de métodos getters e setters. Ao adotar-se a abordagem private, apenas a própria classe pode ler e escrever no atributo. Embora traga proteção, o modificador restringe o acesso ao valor do atributo, que pode ser necessário para a execução de tarefas em outras classes.
Getters e setters
Getters são métodos, usualmente com modificador public, que retornam o valor de um atributo definido como protected ou private. Além de retornar o valor de um atributo, getters podem aplicar formatações ou realizar validações antes de expor o dado. Em analogia ao mundo real, funcionam como um intermediário entre a classe e o interessado no valor do atributo. Em uma loja, por exemplo, o cliente costuma não poder ter acesso ao estoque para obter um produto, devendo solicitá-lo a um recurso acessível oferecido pela loja: o vendedor. Este, sim, terá acesso ao estoque e poderá entregar o item ao cliente. Assim como o cliente não saberá em qual prateleira o produto estava, tampouco com qual identificador, os clientes daquela classe não saberão exatamente como aquele atributo estava implementado.
A implementação do get, por sua vez, também é influenciada pela linguagem de programação. A seguir, serão apresentados dois exemplos, sendo um em Java e outro em TypeScript:
public String getNome() {
return this.nome;
}
get nome(): string{
return this._nome;
}
Enquanto o método getter pode ser utilizado para se obter o valor de um atributo, a edição deste valor pode ser viabilizada para outras classes a partir dos setters. Também de simples implementação, esses métodos podem incluir validações, garantindo que valores indevidos não sejam atribuídos a um atributo.
Uma questão interessante a ser observada é a diferença como Java e TypeScript lidam com os métodos getters e setters. Em Java, esses métodos são declarados de modo igual aos demais, sendo identificados pelo prefixo get ou set antes do nome do atributo ao qual se referem. No caso do atributo nome, por exemplo, o método get relacionado é o getNome(). Assim, em todo o programa, deve-se escrever objeto.getNome() para se obter o valor definido para o atributo nome daquele objeto. Da mesma forma, para se atualizar o valor de um atributo, deve-se adicionar objeto.setNome(“novoNome”). Já em TypeScript, getters e setters são implementados de modo distinto. Assim, tanto o acesso ao valor quanto a alteração ocorrem de forma transparente: objeto.Nome para imprimir e objeto.nome=”novoNome”.
Embora a abordagem apresentada pelo TypeScript torne o processo transparente, similar à implementação tradicional desenvolvida nas linguagens de programação em geral, a estratégia adotada em Java torna evidente que o uso do encapsulamento ocorre naquela classe.
class Pessoa{
private _nome: string;
protected _cpf: string;
constructor(nome: string, cpf: string){
this._nome = nome;
this._cpf = cpf;
}
get nome(): string{
return this._nome;
}
set nome(nome: string){
this._nome = nome;
}
}
Nesse exemplo, o encapsulamento protege o atributo nome (definido como private) e o torna acessível apenas por meio do método público get. Já o atributo cpf, marcado como protected, é acessível apenas pela classe Pessoa e por suas subclasses.
Os modificadores de acesso podem apresentar comportamentos distintos dependendo da linguagem de programação. Por exemplo:
- Em Java, o modificador protected permite o acesso tanto a subclasses quanto a classes que pertençam ao mesmo pacote.
- Em TypeScript, o modificador protected restringe o acesso a subclasses, mas não considera agrupamentos baseados em pacotes, já que a linguagem não adota essa lógica.
Finalmente, há de se mencionar alguns benefícios inerentes ao encapsulamento: Proteção dos Dados: Evita que atributos internos sejam alterados de forma indevida, garantindo maior segurança e previsibilidade. Facilidade de Manutenção: Mudanças na implementação interna de uma classe não impactam outras partes do programa, desde que a interface pública permaneça a mesma. Redução do Acoplamento: Promove a independência entre diferentes partes do sistema, tornando o código mais reutilizável. Com o encapsulamento, o desenvolvedor pode criar sistemas mais robustos e organizados, protegendo a lógica interna das classes enquanto expõe apenas os recursos necessários para interação externa.
Uso cauteloso
Desenvolvedores de software, ao iniciarem seus estudos no paradigma orientado a objetos, entendem que o encapsulamento é um recurso importante e costumam, sem grandes dificuldades, assimilar a boa prática de tornar os atributos da classe como protegidos ou privados. No entanto, muitas vezes implementam getters e setters para todos esses atributos, sem uma reflexão crítica quanto a essa efetiva necessidade. Muitas IDEs, como Netbeans, oferecem recursos automatizados para a geração desses métodos, o que contribui para a sua implementação.
Ocorre que essa prática não é necessariamente adequada, já que a exposição de todos os atributos, especialmente em métodos setters simples (que envolvem apenas a atribuição de valores, sem qualquer verificação ou tratamento) implicam em efeito similar ao do uso do modificador público, tornando o encapsulamento pouco efetivo. Diversos autores, como Robert Martin, discorrem contra essa prática e enfatizam que atributos devem ser expostos quando for estritamente necessário.