[Pesquisar este blog]

quarta-feira, 31 de janeiro de 2018

POO::Fundamentos-08-Encapsulamento

POO-F-07-Construtores POO-F-09-Getters e Setters

O termo encapsulamento se refere ao agrupamento de ideias correlacionadas na forma unidades, módulos ou classes, que passam a ser usadas apenas por meio de seu nome [2]. É um conceito amplo, pois se aplica na construção de métodos, de classes e, portanto, dos objetos, muito importante na OO.

Sabemos que os métodos nada mais são do que funções-membro ou subprogramas que realizam uma tarefa, ou seja, um conjunto organizado de ações relacionadas. Métodos são acionados por meio de seus nomes. Já os objetos novos são criados com o uso do nome de suas classes.

O encapsulamento também determina a representação do objeto, ou seja, possibilita que um objeto seja usado apenas por sua aparência exterior [6], o que pode ser denominado de interface (no sentido de ligação entre as partes -- faces -- externa e interna dos objetos).

Aqui o encapsulamento está muito relacionado a ocultação de informações (data hiding), outra característica da OO, que permite preservar informações importantes dentro do objeto, externando apenas o que se julga conveniente ou necessário. Até mesmo parcelas da própria construção do objeto, ou seja, sua implementação, podem ser escondidas pelo mesmo mecanismo. A ocultação das informações é obtida por meio dos mecanismos de encapsulamento [2][6].

O encapsulamento da implementação ou ocultação das informações, também conhecido como visibilidade ou acessibilidade dos membros de uma classe, é a forma com que seus elementos podem ser vistos e utilizados externamente. Então o encapsulamento define como os membros da classe, isto é, seus atributos, seus métodos e seus construtores da classe poderão ser usados externamente. Aqui é necessário definir mais precisamente os conceitos de interno e externo.

Interno

Interno é aquilo que faz parte da construção/implementação da própria classe. No Java e C# é tudo aquilo que está dentro das chaves que delimitam o corpo da classe. Observe a classe Contador:
class Contador {
   double total;
   Contador() {
      set(100);
   }
   void inc(double d) {
      total = total + d;
   }
   void dec(double d) {
      total = total - d;
   }
   void set(double d) {
      total = d;
   }
}

A variável-membro total, assim como as funções-membro inc(double), dec(double) e set(double), bem como o construtor Contador() são elementos internos da classe Contador. Alguns membros usam outros membros da classe.

Externo
Externo é o uso que as instâncias da classe fazem dos membros desta em outras partes do programa (certamente em outras classes). O uso externo é realizado pelos "clientes" ou "usuários" da classe.
Contador cont = new Contador();
contador.total = 23.5;
contador.dec(0.5);
Neste fragmento, o construtor Contador() é usado externamente à classe Contador, assim como os membros total e dec(double).

A ocultação de dados (data hiding) é muito importante dentro da OO, pois:

  1. permite esconder detalhes da forma de implementação, permitindo resguardar esforços de desenvolvimento;
  2. possibilita criar de mecanismos de consistência, que asseguram que os atributos assumam apenas valores restritos a um conjunto desejado;
  3. garante que operações determinadas sejam realizadas na sequência correta, ou condicionado a estados apropriados do objeto; e
  4. assegura que as instâncias da classe conheçam apenas aquilo que é necessário para o uso dos objetos criados.
O projetista de uma classe tem assim liberdade para determinar quais elementos serão conhecidos e utilizados, separando-os, por meio da ocultação, daqueles que existem apenas para permitir sua implementação. Assim, atributos, métodos e até mesmo construtores que existem numa classe podem ou não ser visíveis externamente.

Em Java, C#, C++ (e outras linguagens OO) a acessibilidade ou visibilidade dos membros da classe é determinada por especificadores de acesso (access specifiers), palavras reservadas destas linguagens que definem seu (grau de) encapsulamento: public, protected e private.

public
A palavra reservada public determina o acesso público. Com ele, membros de uma classe têm utilização irrestrita e podem ser usados livremente pelas suas instâncias. Atributos (variáveis-membro) públicos podem ter seus conteúdos acessados (lidos) e alterados (escritos) sem qualquer restrição; assim como os métodos públicos podem ser acionados arbitrariamente. Qualquer membro declarado público fica, portanto, exposto.
Os membros públicos, vistos em conjunto, compõem a interface da classe, ou seja, são o conjunto de atributos e operações conhecidos e que caracterizam externamente a classe.

private
O acesso privado, indicado pelo especificador de acesso private, define que um método ou atributo de classe nunca deve ser acessível externamente, ocultando completamente tais membros.
Os membros privados usualmente compõem a infraestrutura de uma classe, com atributos e métodos que não precisam ser expostos, mas que contribuem para o funcionamento da classe.

protected
Existe o especificador de acesso protected destinado a indicar membros de uma classe que não devem ser utilizados por meio de suas instâncias, mas apenas na construção de novas classes. Tais membros, ditos protegidos, compõem uma interface de programação da classe, ou seja, um conjunto de atributos e operações destinados exclusivamente aos programadores que usarão tal classe como base para criação de outras, papel que ficará mais claro quando a herança (inheritance) for tratada.

Os especificadores de acesso public, protected e private definem níveis de visibilidade diferentes, ilustrados no quadro seguinte, que considera as situações de implementação (codificação) e instanciação (criação de objetos dentro de programas).


Existe um quarto nível de acessibilidade que é package (ou pacote), que não possui uma palavra reservada para indicá-lo. Por padrão, todos os membros de uma classe têm acesso pacote, exceto quando são aplicados os demais especificadores. Esse foi o acesso definido para todos os membros dos exemplos providos até agora.

Outra observação importante é que todos os membros de uma classe são acessíveis entre si, isto é, os especificadores de acesso só se aplicam externamente à classe.

O encapsulamento, entendido como as restrições aplicadas aos membros de uma classe (ou ocultação de dados -- data hiding), possibilita um controle mais sofisticado para o acesso a tais membros, o que traz várias vantagens [6][7][8]:

  1. o código se torna mais claro, pois ficam evidentes quais membros oferecem funcionalidades reais e quais são auxiliares;
  2. são minimizados os erros de programação, tornando as interfaces mais simples;
  3. classes semelhantes podem exibir uma mesma interface, pois detalhes da implementação permanecem ocultos, facilitando sua utilização;
  4. proporciona facilidades para extensão (criação de novas classes a partir de classes existentes); e
  5. oculta a realização de modificações e alterações, pois quando a interface não é afetada, as mudanças são transparentes para os usuários da classe.
O encapsulamento proporciona a separação do projeto (design) da implementação (coding), facilidades de modificação e padronização do código. Isto implica em maiores chances de extensão, redução do tempo de desenvolvimento e reusabilidade real, ou seja, as maiores vantagens da programação orientada a objetos [8].

Em Java e C# a declaração de classes também emprega os especificadores de acesso public (caso mais comum) e private, mas esse último apenas em situações especiais. Quando uma classe Java é declarada como pública, o arquivo que a contém deve possuir o mesmo nome, para facilitar sua localização.

No exemplo abaixo, a classe denominada Acesso contém quatro campos inteiros com todos os níveis de acesso (público, protegido, privado e pacote); e dois métodos com níveis público e privado. O único construtor é público, situação típica para possibilitar a criação de objetos das classes (embora possam ser declarados com outras visibilidades).

// Arquivo Acesso.java
public class Acesso {
   public int publico;
   protected int protegido;
   private int privado;
   int pacote;

   public Acesso() {
      prepare();
   }

   public String toString() {
      return String.format(
         "publico=%d\nprotegido=%d\nprivado=%d\npacote=%d\n",
         publico, protegido, privado, pacote);
   }

   private void prepare() {
      publico = 7;
      pacote = 5;
      protegido = 3;
      privado = 1;
   }

   public void reset() {
      publico = 0;
      pacote = 0;
      protegido = 0;
      privado++;
   }
}

Observe também que o método privado prepare() é usado pelo construtor público, pois os especificadores de acesso não estabelecem restrições internas, isto é, entre os membros da própria classe. Já o método reset() altera os campos protegido, zerando-o, e privado, incrementando-o.

A classe TestaAcesso possui apenas o método main, que instancia um objeto do tipo Acesso. Com a variável de instância denominada acesso, é acionado o método público toString(), cujo resultado é exibido no console, mostrando o estado inicial do objeto recém-criado. Depois, os campos público e pacote tem seus valores alterado; novamente o estado é exibido; então é acionado o outro método público reset(); e reexibido o estado do objeto, que retorna a situação inicial.

// Arquivo TestaAcesso.java
public class TestaAcesso {
   public static void main(String[] a) {
      Acesso acesso = new Acesso();
      System.out.println(acesso.toString());
      acesso.publico = 1000;
      acesso.pacote = 777;
      System.out.println(acesso.toString());
      acesso.reset();
      System.out.println(acesso.toString());
   }
}

Apenas os campos públicos podem ser acessados pelas instâncias da classe. A indicação de campos privados ou protegidos será apontada como erro durante a compilação, impedindo a geração e execução de código inválido.

O programa TestaAcesso, como em outros fragmentos visto até aqui, mostra como usar um objeto de outra classe.

Na próxima lição veremos como utilizar os especificadores de acesso para prover o encapsulamento efetivo de elementos de uma classe.

POO-F-07-Construtores POO-F-09-Getters e Setters

Referências Bibliográficas

[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

Nenhum comentário: