O que é encapsulamento na programação orientada a objetos
O encapsulamento acontece principalmente em programação orientada a objetos (POO), em que objetos são utilizados para representar entidades do mundo real e encapsulam os dados e os comportamentos relacionados a esses elementos. A técnica também pode ser aplicada em outras áreas da programação, como em bibliotecas e frameworks, com técnicas de encapsulamento para esconder detalhes de implementação de um recurso e expor somente as operações necessárias para o uso dele.
Por que usar encapsulamento?
O encapsulamento permite que os detalhes de implementação de um objeto sejam alterados sem afetar o restante do sistema, tornando o código mais flexível e fácil de manter. Com isso, os objetos podem ser reutilizados em diferentes partes do sistema sem que haja preocupação com os detalhes de implementação.
A técnica facilita a realização de testes unitários, pois permite testar o comportamento dos objetos isoladamente, sem depender de outros objetos ou de partes do sistema. Sem contar que pode ser usada para proteger dados e métodos de uma classe de acesso indevido, impedindo a violação de informações confidenciais.
Publique suas aplicações em um Servidor VPS com Docker embarcado a partir de 2 GB
Como o encapsulamento funciona?
O encapsulamento funciona com a utilização de modificadores de acesso para restringir o acesso aos atributos e aos métodos de um objeto. Garante que somente as classes apropriadas possam acessar as informações e ajuda a assegurar que os atributos e os métodos sejam usados de forma consistente e previsível.
Os modificadores de acesso mais comuns são:
- private: os atributos e os métodos marcados como “private” só podem ser acessados dentro da própria classe em que foram declarados;
- protected: os atributos e os métodos marcados como “protected” podem ser acessados dentro da própria classe e das subclasses;
- public: os atributos e os métodos marcados como “public” podem ser acessados por qualquer classe.
Algumas linguagens de programação têm outros modificadores, como “internal” e “package-private”, que variam de acordo com a linguagem, mas em geral têm a mesma funcionalidade de proteção de acesso. O uso correto desses modificadores de acesso é o que garante a segurança e a integridade do código.
A divisão clara da responsabilidade permite que cada classe seja projetada para fornecer uma funcionalidade específica e garantir que os atributos e os métodos sejam usados de forma consistente e previsível. Quando cada classe tem uma responsabilidade clara, os atributos e os métodos de cada uma podem ser projetados para atender às necessidades específicas dessa classe, sem preocupação com as necessidades de outras classes.
Encapsulamento e os outros pilares da POO
O encapsulamento é um dos quatro pilares da programação orientada a objetos, ao lado de abstração, herança e polimorfismo. Os quatro funcionam juntos, e entender a relação entre eles ajuda a decidir o que esconder e o que expor em cada classe.
Abstração
A abstração define o que um objeto faz do ponto de vista de quem o usa, sem entrar em como ele faz. O encapsulamento é o mecanismo que torna essa separação real no código: a interface pública representa a abstração, e os atributos privados guardam os detalhes que ficam de fora dela.
Herança
Na herança, uma subclasse reaproveita atributos e métodos de uma classe-mãe. É aqui que o modificador protected entra em cena, liberando o acesso para as subclasses sem abrir os dados para o restante do sistema. Manter atributos como private e oferecer métodos protected para as subclasses reduz o acoplamento entre elas e evita que uma mudança na classe-mãe quebre todas as filhas.
Polimorfismo
O polimorfismo permite que objetos de classes diferentes respondam à mesma chamada de método, cada um com a própria implementação. Isso só funciona bem quando o código que chama o método depende apenas da interface pública. Se ele acessa atributos internos de cada classe, trocar uma implementação por outra deixa de ser transparente.
O que pode ser encapsulado?
O encapsulamento pode ser aplicado em várias linguagens de programação. Em geral, a maioria das linguagens orientadas a objetos, como Java, C#, C++, Python, Ruby, JavaScript e PHP, tem mecanismos para encapsulamento, mesmo que as sintaxes e as nomenclaturas possam variar.
Além das linguagens orientadas a objetos, existem outros exemplos de encapsulamento em diferentes áreas, como:
- rede: a ferramenta adiciona cabeçalhos e trailers a pacotes de dados para permitir o transporte de dados por diferentes tipos de rede;
- microcontroladores: usados para criar uma camada de abstração entre o hardware e o software, permitindo que o software acesse o hardware de forma simples e intuitiva;
- bibliotecas, componentes e serviços: escondem os detalhes de implementação dessas funções e procedimentos, expondo somente a interface pública.
Como fazer o encapsulamento?
Para encapsular atributos de uma classe é necessário os declarar com um modificador de acesso private. Por exemplo, se você tem uma classe “Pessoa” e quer encapsular o atributo “nome”, pode o declarar como “private”:
class Pessoa {
private:
string nome;
//…
};
Para encapsular métodos de uma classe, é necessário os declarar com um modificador de acesso private ou protected. Por exemplo, se você tem uma classe “Pessoa” e quer encapsular o método “setNome”, pode o declarar como “private”:
class Pessoa {
private:
string nome;
void setNome(string nome);
//…
};
Para acessar os atributos e os métodos encapsulados, é preciso usar métodos públicos chamados getters e setters, que permitem acessar e modificar os atributos encapsulados de forma controlada. Por exemplo, se você tem uma classe “Pessoa” com um atributo “nome” encapsulado, pode criar métodos públicos “getNome” e “setNome” para acessar e modificar esse atributo:
class Pessoa {
private:
string nome;
public:
string getNome();
void setNome(string nome);
//…
};
Dessa forma, pode-se garantir que somente as operações permitidas serão realizadas sobre os atributos e os métodos encapsulados, evitando problemas como acesso indevido e violação de regras de negócio.
Exemplos de encapsulamento em outras linguagens
Os exemplos acima usam C++. A mesma ideia aparece nas linguagens mais usadas no desenvolvimento web, com sintaxes diferentes. Para comparar, os exemplos a seguir usam a mesma classe “ContaBancaria”, em que o saldo só pode ser alterado por métodos que validam a operação. Se a lógica de programação por trás das condições ainda for novidade para você, revise esse conceito antes.
Java
Em Java, o controle é feito com as palavras-chave private, protected e public, descritas na documentação oficial da Oracle sobre controle de acesso:
public class ContaBancaria {
private double saldo;
public double getSaldo() {
return saldo;
}
public void depositar(double valor) {
if (valor <= 0) {
throw new IllegalArgumentException("O depósito deve ser positivo");
}
saldo += valor;
}
public void sacar(double valor) {
if (valor > saldo) {
throw new IllegalStateException("Saldo insuficiente");
}
saldo -= valor;
}
}
Python
Python não tem modificadores de acesso como Java. Por convenção, um sublinhado no início do nome indica que o atributo é interno, e dois sublinhados ativam o name mangling, que dificulta o acesso externo, conforme a documentação do Python sobre variáveis privadas. O decorador @property expõe a leitura sem liberar a escrita direta:
class ContaBancaria:
def __init__(self):
self.__saldo = 0.0
@property
def saldo(self):
return self.__saldo
def depositar(self, valor):
if valor <= 0:
raise ValueError("O depósito deve ser positivo")
self.__saldo += valor
def sacar(self, valor):
if valor > self.__saldo:
raise ValueError("Saldo insuficiente")
self.__saldo -= valor
PHP
O PHP usa os mesmos três níveis de visibilidade, explicados no manual do PHP sobre visibilidade:
<?php
class ContaBancaria {
private float $saldo = 0.0;
public function getSaldo(): float {
return $this->saldo;
}
public function depositar(float $valor): void {
if ($valor <= 0) {
throw new InvalidArgumentException("O depósito deve ser positivo");
}
$this->saldo += $valor;
}
public function sacar(float $valor): void {
if ($valor > $this->saldo) {
throw new RuntimeException("Saldo insuficiente");
}
$this->saldo -= $valor;
}
}
JavaScript
Em JavaScript, atributos e métodos com o prefixo # são privados e a própria linguagem bloqueia o acesso fora da classe, como mostra a documentação da MDN sobre elementos privados:
class ContaBancaria {
#saldo = 0;
get saldo() {
return this.#saldo;
}
depositar(valor) {
if (valor <= 0) throw new Error("O depósito deve ser positivo");
this.#saldo += valor;
}
sacar(valor) {
if (valor > this.#saldo) throw new Error("Saldo insuficiente");
this.#saldo -= valor;
}
}
Nos quatro casos, tentar alterar o saldo diretamente de fora da classe gera erro ou exige contornar a convenção de forma explícita. A regra de negócio fica concentrada em um único lugar.
Situações em que o encapsulamento evita bugs
Os problemas que o encapsulamento previne costumam aparecer quando o sistema cresce e mais pessoas mexem no mesmo código. Algumas situações recorrentes em projetos reais mostram o efeito da técnica.
Estado inválido por atribuição direta
Com o saldo público, qualquer trecho do sistema pode escrever algo como conta.saldo = -500, e o erro só aparece mais tarde, em um relatório ou em uma cobrança indevida. Com o atributo privado e a validação dentro de sacar(), o valor inválido é barrado no momento em que alguém tenta gravá-lo, e a pilha de erro aponta exatamente para a origem do problema.
Coleção interna alterada por fora
Um método getItens() que devolve a lista original de um pedido permite que outra parte do código adicione ou remova itens sem passar pelo recálculo do total. O resultado é um pedido com itens e valor que não batem. Devolver uma cópia ou uma versão somente leitura da lista mantém o total sempre coerente com os itens.
Mudança de implementação que quebra o sistema
Imagine que o preço de um produto passa a ser armazenado em centavos, como número inteiro, para evitar erros de arredondamento com casas decimais. Se o atributo for acessado diretamente em dezenas de arquivos, cada um deles precisa ser alterado, e qualquer ponto esquecido passa a exibir valores cem vezes maiores. Com o atributo encapsulado, a mudança fica restrita à classe e o método getPreco() continua devolvendo o mesmo formato para o restante do sistema.
Transições de status fora de ordem
Em um fluxo de pedidos, marcar como “enviado” um pedido que ainda não foi pago gera problemas de logística e financeiros. Quando o status é privado e só muda por métodos como confirmarPagamento() e enviar(), cada método confere se a transição é válida antes de aplicá-la.
Quando usar encapsulamento?
O encapsulamento deve ser usado sempre que possível em programação orientada a objetos, pois oferece vários benefícios, como proteção de dados, modificabilidade, reutilização de código, testabilidade e segurança.
A ferramenta é útil para proteger os dados de um objeto de acesso indevido ou modificação acidental, alterar os detalhes de implementação de um objeto sem afetar o restante do sistema e reutilizar um objeto em diferentes partes do sistema sem preocupação com os detalhes de implementação.
O encapsulamento é uma técnica importante para melhorar a qualidade do código, garantir a segurança e a confidencialidade dos dados, bem como facilitar a manutenção e a evolução do sistema. É uma boa prática que deve ser usada sempre que possível.
Conheça os Servidores VPS da Locaweb, com infraestrutura no Brasil e suporte em português