O que é encapsulamento na programação orientada a objetos

mulher programadora em frente a uma tela de computador

    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

    O autor

    Rodrigo Cardoso (Pokemaobr)

    Conhecido como Poke, é Streamer (Live "Coder") na Twitch, Web Developer e apresentador do talk show "The Velopers". Com bacharelado em Matemática e MBA em SOA, Poke atua como desenvolvedor e organizador de eventos de TI. É evangelista PHPSP e criador do PokePHP, focando em disseminar conteúdos técnicos e humor para a comunidade de desenvolvedores. Nas horas vagas, ele adora se conectar com a comunidade e compartilhar seu conhecimento de maneira divertida e informativa.

    Mais de Rodrigo Cardoso (Pokemaobr)