[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.

domingo, 28 de janeiro de 2018

POO::Fundamentos-07-Construtores

POO-F-06-Sobrecarga POO-F-08-Encapsulamento
A existência de qualquer tipo de objeto, tanto no mundo real como na POO, exige que ele seja previamente criado, possibilitando que seja usado. As classes são novos tipos de dados definidos pelo programador para descrever entidades reais ou abstratas; que servem como modelo para criação dos objetos do seu tipo.

Cada objeto é uma ocorrência de uma classe, ou seja, é uma entidade distinta das demais, capaz de armazenar dados e realizar algumas ações, tal como descrito pelo seu tipo, isto é, por sua classe. Assim, vimos que o processo de criação de objetos implica em aspectos fundamentais da OO [6][7]:
  • Todo objeto é de um tipo específico/classe específica.
  • Os objetos da mesma classe, ou seja, do mesmo tipo, possuem as mesmas características (atributos) e comportamento (operações).
  • Todo objeto criado é distinto dos demais, independente de seus tipos.
  • Todo objeto tem um ciclo de vida.
  • Todo objeto é único e pode ser distinguido dos demais.
Por conta disso, a criação de objetos é uma operação essencial, denominada instanciação, muito importante na OO, que envolve um operador próprio (new nas linguagens de programação Java e C#) e também uma operação especial responsável pela inicialização dos objetos criados, chamada de construtor [4][7][11].

Um construtor é como um método ou função-membro, mas cuja responsabilidade é prover a preparação dos objetos de sua própria classe, pois os atributos (variáveis-membro) devem ser inicializados de modo adequado para que os objetos sejam consistentes e possam ser usados adequadamente. 

Embora os construtores sejam como métodos destinados à inicialização dos objetos da classe, são diferenciados das demais operações por três características [6]:
  • possuem, sempre, o exato mesmo nome da classe;
  • não declaram um valor de retorno, nem mesmo void, pois "retornam" um novo objeto de seu tipo; e
  • só podem ser acionados por meio do operador de instanciação new, ou seja, seu uso fica restrito à criação de novos objetos.
De maneira geral, a instanciação de objetos usa a sintaxe abaixo:

<Classe> objeto = new <construtor>([lista_argumentos]);

Aqui:
  • Classe indica o nome da classe que determina o tipo do objeto;
  • objeto é a variável que "contém" o objeto;
  • new é o operador especial de instanciação;
  • construtor é a operação especial da classe que prepara os novos objetos do tipo; e
  • lista_argumentos opcionalmente fornece dados para a criação do novo objeto.
A figura que segue ilustra o uso típico de construtores para uma classe Student.


Os construtores retornam uma referência para o objeto criado (que pode ser algo como seu endereço na memória ou um identificador próprio do sistema OO usado) que deve ser armazenada numa variável do tipo do objeto. Após a instanciação, o objeto criado fica disponível para utilização por meio da variável de referência, o que possibilita acessar seus atributos ou acionar seus métodos:

variavelObjeto.atributo = <expressaoTipoAtributo>;
variavalObjeto.método([listaParametros]);

Os atributos e métodos também podem ser utilizados em expressões compatíveis com o tipo do atributo ou do valor retornado pelo método.

Observe a classe Circulo, cujo código é equivalente em Java ou C#:

class Circulo {
   double raio;

   Circulo() { 
      raio = 1;
   }
   double area() {
      return 3.1415926*raio.raio;
   }
}

O tipo Circulo possui todos os elementos possíveis de uma classe [5][6][7][11]:
  • um atributo raio, que descreve uma característica dos objetos tipo círculo;
  • uma operação area(), funcionalidade que provê o cálculo da área do círculo baseada no atributo da classe;
  • um construtor Circulo(), que tem o mesmo nome da classe e inicializa novos objetos deste tipo com o valor padrão 1 para o raio.
Agora objetos do tipo Circulo podem ser criados e utilizados.

// Java
Circulo circulo = new Circulo();
System.out.println("Raio = " + circulo.raio);
System.out.println("Area = " + circulo.area());

//C#
Circulo circulo = new Circulo();
circulo.raio = 2.5;
Consule.WriteLine("Area = " + circulo.area());

Quando declaramos uma classe e não especificamos um construtor, o compilador automaticamente gera um construtor denominado default, que não realiza qualquer ação, apenas permitindo que novos objetos sejam criados. Se o programador fornece qualquer construtor, o compilador não incluir o construtor default.

Embora os atributos (campos) da classe sejam automaticamente inicializados (variáveis numéricas recebem zero, boleanas recebem false e de tipo objeto recebem null), é adequado que o programador, explicitamente, acrescente o construtor default (sem parâmetros) tornando clara a inicialização desejada para novos objetos da classe.

Tal como métodos, os construtores podem possuir parâmetros, permitindo que a criação dos objetos seja customizada. Assim como para os métodos, a sobrecarga (overload) também pode ser usada com construtores, é bastante conveniente em casos como este, para estabelecer formas diferentes para a criação de objetos.

Com uso da sobrecarga de construtores (constructor overload), a classe Circulo pode ser modificada para possuir tanto o construtor default (sem parâmetros), como um construtor parametrizado com o tamanho do círculo criado.

class Circulo {
   double raio;

   Circulo() { 
      raio = 1;
   }
   Circulo(double r) { 
      raio = r;
   }
   double area() {
      return 3.1415926*raio.raio;
   }
}

Com a presença de construtor parametrizado, a criação de novos objetos do tipo Circulo passa a exigir a indicação de um argumento que especifica o valor do raio do círculo criado, customizando a criação de objetos. Com isso temos:

Circulo outroCirculo = new Circulo(2.5);

A existência de construtores parametrizados torna mais flexível a criação de objetos, cujos campos podem ser iniciados com valores diversos adequados a cada programa. Somente com o construtor default, temos duas operações para criar um círculo customizado:

Circulo outroCirculo = new Circulo();
outroCirculo.raio = 2.5;

É muito importante observar que se forem fornecidos um ou mais construtores parametrizados, o compilador não gera o construtor default nesta situação. Quando qualquer construtor é fornecido pelo programador, o compilador não faz a adição automática do construtor default, de modo que, se desejado, o construtor default deve ser manualmente introduzido.

Sem o construtor default, torna-se obrigatório que os argumentos exigidos pelos construtores parametrizados sejam fornecidos. Isto é como afirmar que novos objetos não podem ser criados sem tais dados, o que é consistente com muitas situações da realidade: criar uma conta corrente exige um argumento com o CPF de seu titular; matricular um aluno numa disciplina requer um argumento com seu RA; fazer algum cadastro na web precisa de um endereço de e-mail; e assim por diante.

O uso de construtores permite, então, definir como novos objetos de uma classe serão construídos, possibilitando exigir dados na ocasião de sua instanciação, permitindo tanto sua customização, quanto economizando código no preparo de novas instâncias.

POO-F-06-Sobrecarga POO-F-08-Encapsulamento

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 Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.

quinta-feira, 25 de janeiro de 2018

POO::Fundamentos-06-Sobrecarga

POO-F-05-Métodos POO-F-07-Construtores
A sobrecarga de métodos (method overload) é a possibilidade de existirem várias operações com o mesmo nome em uma mesma classe [6][7]. É a manifestação mais simples do polimorfismo, uma das características mais importantes da OO.

Isto significa que dentro de uma classe podem existir métodos homônimos (com o mesmo nome), mas desde que possuam assinaturas (signatures) diferentes. A assinatura de um método, ou sua method signature, é a lista dos tipos de seus parâmetros formais, na exata ordem em que foram declarados, mesmo que existam repetições.

Sendo assim uma operação é considerada sobrecarregada (overloaded) quando existirem várias declarações envolvendo um mesmo nome, mas sempre com assinaturas diferentes (na quantidade ou no tipo de seus parâmetros).

Considere a classe Contador, criada com o propósito de armazenar o valor atual de uma contagem, além de prover vários métodos sobrecarregados de incremento, denominados inc, como no código que segue.

class Contador {
   double contagem;

   Contador() {
      contagem = 0;
   }
   void inc() {
      contagem = contagem + 1;
   }
   void inc(int i) {
      contagem = contagem + i;
   }
   void inc(double d) {
      contagem = contagem + d;
   }
}

Neste exemplo, existem três métodos denominados inc, cujas assinatura são:
  • inc(void)
  • inc(int)
  • inc(double)
Assim, inc é um método sobrecarregado, dotado de três versões que permitem incrementar o valor da contagem (um atributo interno da classe) com, respectivamente, o valor default um (um incremento unitário portanto), um valor inteiro indicado como argumento; ou um valor double dado como argumento. O uso do mesmo nome é apropriado por que os três métodos fazem, conceitualmente, a mesma operação de incrementar o valor da contagem, assim a sobrecarga de métodos nesta situação é adequada porque existem maneiras diferentes de realizar-se tal operação.

Conforme o tipo e valor dos argumentos fornecidos, é selecionado o método adequado na classe, como na figura acima e no fragmento de código que segue.

// cria novo contador iniciado em zero
Contador cont = new Contador();

// efetua incrementos com operações sobrecarregadas
cont.inc(); // incremento unitário default
cont.inc(5); // incremento de cinco unidades inteiras
cont.inc(3.5); // incremento real de 3.5

System.out.println(cont.contagem); // exibe o resultado 9.5

Destacamos que o valor de retorno não faz parte da assinatura, pois a resolução da sobrecarga, isto é, a seleção do método adequado se dá pelo tipo e número dos argumentos fornecidos. A resolução da sobrecarga emprega uma série de critérios que visa fazer a melhor escolha possível considerando os tipos dos argumentos na chamada e as versões disponíveis da função sobrecarregada, mas a ambiguidade não é permitida, exigindo que cada método sobrecarregado tenha uma assinatura distinta (listas distintas de número e tipos dos parâmetros formais).

Outro exemplo do emprego da sobrecarga poderia ser na operação de um sub-array a partir de um array. Dado um array de n posições, um outro inteiro m poderia indicar o tamanho da porção inicial do sub-array (desde que m<=n). Para obter uma porção intermediária do array de n posições, deveriam ser especificados inteiros i e f, onde i<=f<=n. Um sub-array da parte final pode ser obtido com esta mesma operação. Neste caso temos duas versões de obtenção de sub-array:
  • uma com dois parâmetros, o array e o tamanho m inteiro da porção inicial a ser extraída;
  • outra com três parâmetros, o array, a posição inicial i inteira e a posição final f inteira da porção a ser extraída.
E qual o tipo do array? Para cada tipo de array deveriam ser fornecidas as duas operações, evitando que o usuário da operação precise realizar coerção entre tipos! Assim, uma classe SubArray poderia ter este par de métodos para todos os tipos primitivos do Java (byte, short, int, long, float, double, boolean e char) e também para o tipo Object. Em C# a ideia é a mesma.

Uma versão "incompleta" de SubArray, apenas para o tipo int, poderia ser como segue:

class SubArray {
// obtém subarray inicial com tamanho m (posição m não inclusa)
   int[] subArray(int[] a, int m) {
      // verifica se tamanho m é válido
      if (m > a.length) {
         throw new RuntimeException("valor invalido de m.");
      }
      // aloca novo array
      int[] aux = new int[m];
      // copia elementos
      for(int p=0; p<m; p++) aux[p] = a[p];
      // retorna resultado
      return aux;
   }
// obtém subarray entre início (i-incluso) e fim (f- não incl)
   int[] subArray(int[] a, int i, int f) {
      // verifica se posições são válidas
      if (i > f || f > a.length) {
         throw new RuntimeException(
             "valores invalidos de i e/ou f.");
      }
      // aloca novo array
      int[] aux = new int[f-i];
      // copia elementos
      for(int p=i; p<f; p++) aux[p] = a[p];
      // retorna resultado
      return aux;
   }
}

Para usar esta classe basta:

// dispor de um array (de inteiros)
int[] array = { 0, 1, 2, 3, 4, 5, 6 }; // inicialização automática

// instanciar objeto da classe SubArray
SubArray obj = new SubArray();

// obtém subarray inicial {0,1,2}
int[] subArrayInicio = obj.subArray(array, 3);

// obtém subarray intermediário {3,4,5}
int[] subArrayMeio = obj.subArray(array, 3, 6);

// obtém subarray final {3,4,5,6}
int[] subArrayFim = obj.subArray(array, 3, 7);

// obtém (sub)array cópia {0,1,2,3,4,5,6}
int[] arrayCopia = obj.subArray(array, 0, 7);

A classe SubArray poderia ser melhorada, com uso de métodos estáticos e fatoração do próprio código, evitando a repetição de código semelhante, o que será feito na lição sobre membros estáticos.

A sobrecarga também pode ser usada com construtores, como será visto na próxima lição, tornando-a muito conveniente para estabelecer também formas diferentes para a criação de objetos.

Concluindo, a sobrecarga (method overload) é o emprego do mesmo nome para operações sobre parâmetros de diferentes tipos [4][6][7][9], o que representa uma forma do polimorfismo. Seu emprego possibilita a construção de classes versáteis, onde a realização de certas operações admitem várias formas, cada qual conveniente para uma situação da programação, mais apropriada para cada uso.

POO-F-05-Métodos POO-F-07-Construtores

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.

terça-feira, 23 de janeiro de 2018

POO::Fundamentos-05-Métodos

POO-F-04-Atributos POO-F-06-Sobrecarga
Sabemos que uma classe é um novo tipo de dados que pode ser definido pelo programador para descrever uma entidade real ou abstrata, ou seja, é um que especifica os objetos de um tipo determinado, por meio de uma descrição genérica de todos os elementos pertencentes ao conjunto que está sendo modelado.

Como ilustrado na figura acima, as classes possuem:
  • atributos, que descrevem as características dos objetos (sua aparência, sua constituição e seu estado); e
  • operações, que descrevem suas ações ou capacidades, realizadas sobre seus próprios atributos.
As classe são, portanto, constituídas por seus atributos (ou campos) e suas operações (ou métodos), os quais são conhecidos como membros da classe [5][6][7][8]. Assim, os atributos são as variáveis-membro e as operações são as funções-membro da classe. Portanto, uma classe incorpora o conceito de tipo abstrato de dados, que define um conjunto de valores que representam este tipo, assim como as operações possíveis sobre tais valores [10]. 

Enquanto os atributos permitem armazenar informações relativas aos objetos, os métodos (operações ou funções-membro) realizam ações sobre estes atributos, permitindo especificar modificações possíveis em um objeto. Isto significa que os métodos conferem um caráter dinâmico aos objetos, pois permitem que exibam um comportamento que, em muitos casos, pode simular ou imitar o comportamento de um objeto real.

Outra forma de entender o que são os métodos é imaginar que os objetos são capazes de enviar e receber mensagens, tais como comandos, de forma programas possam ser construídos com o uso de objetos que interagem entre sim trocando mensagens e  proporcionando o comportamento desejado. A metáfora aqui é que a ação de ligar um aparelho, acionando o controle apropriado, corresponde à mensagem "ligar". Quando realizamos esta ação no mundo real não é necessário que compreendamos os detalhes de funcionamento do aparelho em questão, mas apenas entender (superficialmente) como operá-lo.

Os métodos são funções agregadas em uma classe, ou seja, são blocos de código aos quais se associam um nome, podendo retornar um valor de tipo específico e também receber valores (seus argumentos). Os métodos encapsulam funcionalidades ou capacidades em uma classe, constituindo um repertório de operações disponíveis 'naturalmente' para seus objetos [6].

Tal como na matemática, uma função pode declarar parâmetros formais, que serão supridos pelos argumentos fornecidos no uso da função, que tem o papel de entrada de dados para a função. O valor de retorno é o resultado da função, que tem papel de saída da função. Para qualquer valor válido de entrada (domínio da função) é produzido um valor correspondente (imagem da função).

Os métodos são acionados por meio das instâncias de sua classe, ou seja, os objetos de seu tipo, produzindo resultados ou transformações nos atributos pertencentes ao objeto que os acionou, sem afetar os demais objetos da classe.

Para usarmos um método de uma classe procedemos de forma análoga ao uso de seus atributos, isto é, escolhemos o objeto através de seu identificador e com o operador seletor ('.') indicamos qual método deve ser acionado, suprindo os argumentos necessários tal como para funções, assim a chamada de um método tem a forma [4][7]:

identificadorDoObjeto.nomeDoMétodo(listaDeArgumentos)

Uma classe Caixa pode ser definida, em Java ou C# como segue, caracterizando suas dimensões e seu peso.

class Caixa {
   double altura, largura, profundidade;
   double peso;
}

Desta maneira, objetos do tipo Caixa poderiam apenas representar caixas reais, sem prover qualquer outra facilidade.

Mas seria conveniente que a classe Caixa dispusesse de uma operação para determinar seu volume, pois como esta medida está diretamente associada às dimensões do objeto, que são atributos da classe, é simples seu cálculo, não sendo necessário armazenar tal informação. Para declararmos um método em um classe basta definir uma função dentro do corpo da classe.

class Caixa {
   double altura, largura, profundidade;
   double peso;

   double volume() {
      double res = altura * largura * profundidade;
      return res;
   }
}

Devem ser observados os seguintes aspectos neste método:
  • double volume () { ... }
    representa a declaração do método, como uma função denominada volume, que retorna um resultado do tipo double. Esta função não toma (não recebe) argumentos.
  • return res;
    no código (no corpo) do método, a diretiva return tanto encerra sua execução, retornando ao ponto onde foi acionada, como permite o retorno do resultado da função.
  • double res = altura * largura * profundidade;
    no corpo do método é possível tanto declarar-se variáveis locais, cujo escopo é limitado ao corpo do método, como utilizar os atributos/campos da classe.
O método volume() adiciona uma funcionalidade/capacidade à classe Caixa, que é determinar seu próprio volume, como um serviço. O código do exemplo é simples, constituído de uma expressão algébrica, mas poderia ser bastante mais complexo, utilizando variáveis de tipo primitivo ou outros objetos para determinar seu resultado.

O programa Java que segue mostra a criação e uso de um objeto do tipo Caixa, com sua instanciação, definição de suas dimensões (uso dos atributos) e exibição do seu volume (uso do método) [7].

class TestaCaixa {
   public static void main(String[] arg) {
      // instanciação do objeto tipo Caixa
      Caixa caixa = new Caixa();
      // definição de dimensões (uso dos atributos)
      caixa.altura = 1.0;
      caixa.largura = 2.5;
      caixa.profundidade = 3.0;
      // determinação e exibição do volume (uso do método)
      System.out.println("Volume = " + caixa.volume());
   }
}

Segue o programa equivalente em C# [11]:

class TestaCaixa {
   static void main() {
      // instanciação do objeto tipo Caixa
      Caixa caixa = new Caixa();
      // definição de dimensões (uso dos atributos)
      caixa.altura = 1.0;
      caixa.largura = 2.5;
      caixa.profundidade = 3.0;
      // determinação e exibição do volume (uso do método)
      Console.WriteLine("Volume = " + caixa.volume());
   }
}

Quando executados, estes programas exibem, no console, o resultado:

> Volume = 7.5

Consideremos agora um outro problema, de natureza financeira. É sabido que o valor futuro de um investimento inicial (ou valor presente) é uma função do tempo de aplicação e da taxa de juros, como mostra a relação que segue:

ValorFuturo = ValorPresente * (1 + Juros)^Tempo

Aqui o tempo é número inteiro de períodos de incidência da taxa de juros. Se os juros são diários, o tempo é dado em dias; se os juros são mensais, o tempo é dados em meses; e assim por diante. O valor presente e os juros são valores reais.

Poderia ser modelada uma classe para armazenar o valor presente e a taxa de juros, de maneira que o valor futuro fosse calculado em função do tempo de aplicação. Analise o código Java que segue:

class Investimento {
   double valorPresente;
   double juros;

   double valorFuturo(int tempo) {
      return valorPresente * Math.pow(1 + juros, tempo);
   }
}

Na classe Investimento, o método valorFuturo toma um argumento do tipo int para ser acionado. Tal argumento, denominado tempo, funciona como uma variável local no código do método. Para acionar o método, deve ser fornecido um inteiro, como por exemplo, considerando a existência de um objeto do tipo Investimento denominado investimento:

investimento.valorFuturo(6);

Esta classe poderia ser usada como mostra o fragmento seguinte:

Investimento inv1 = new Investimento();
inv1.valorPresente = 1000.00; // Valor inicial = R$1000.00
inv1.juros = 0.01; // Juros de 1% ao mês
for(int t=1; t<=4; t++) {
    System.out.printf("Mes: %d ValorFuturo=R$%f\n", t, inv1.valorFuturo(t));
}

O resultado deste fragmento é:

Mes: 1 ValorFuturo=R$1010,000000
Mes: 2 ValorFuturo=R$1020,100000
Mes: 3 ValorFuturo=R$1030,301000
Mes: 4 ValorFuturo=R$1040,604010

Conforme a necessidade do modelo programado, podem ser definidos tantos métodos quantos necessários em uma classe, com nenhum, um ou mais argumentos, utilizando qualquer combinação dos campos da classe e, até mesmo, outros métodos da própria classe, além de variáveis locais de qualquer tipo válido.

Isto permite adicionar funcionalidades às classes, o que abre um universo de possibilidades para a programação e uso dos objetos.

POO-F-04-Atributos POO-F-06-Sobrecarga

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.

sexta-feira, 19 de janeiro de 2018

POO::Fundamentos-04-Atributos

POO-F-03-Criação de Objetos POO-F-05-Métodos
Uma classe é um novo tipo de dados que pode ser definido pelo programador para descrever uma entidade real ou abstrata. Podemos entendê-las como um modelo ou como uma especificação para objetos de um tipo determinado, ou seja, descrevem de maneira genérica os elementos pertencentes a um dado conjunto. 


Em um programa, as classes representam os conceitos fundamentais da aplicação em termos da realidade 'modelada' por tais classes [9]. Para a programação, criar classes envolve dois aspectos essenciais: a modelagem dos objetos a serem representados e a implementação deste modelo.

Modelar uma classe é determinar uma descrição adequada e geral para o conjunto de objetos que desejamos representar, ou seja, para todas as sua ocorrências. 

Uma bola qualquer, cuja forma seja esférica, pode ser descrita por seu tamanho, cor, peso, material etc. Um modelo de bola, destinado a uma loja de materiais esportivos, talvez necessite incorporar outras características ao modelo da bola, agora vista como um produto. Já um clube que deseja controlar o empréstimo de seu material recreativo pode exibir diferentes necessidades. Assim, o mesmo tipo de objeto pode ser descrito de maneiras diferentes, conforme a aplicação do modelo, revelando a maior dificuldade desta tarefa: estabelecer modelos apropriados para as aplicações pretendidas e não apenas para descrever os objetos modelados.

As características que descrevem os objetos de uma classe são chamadas de atributos. Alguns atributos descrevem a aparência (o "ser") de um objeto, como sua cor, dimensões, peso etc.); enquanto outras representam estados (o "estar"). Uma lâmpada comum opera numa voltagem particular, com uma potência máxima, estando acesa ou apagada. Por exemplo, a lâmpada é 110V e  é 40W e está apagada. 

Uma classe Lampada pode ser declarada em Java ou C# como:

class Lampada {
   double voltagem;
   double potencia;
}

Cada atributo (ou campo) é como uma variável que faz parte da classe, por isso a outra denominação técnica de variável-membro, cuja sintaxe é:

class <NomeClasse> {
   [acesso] <tipo> <nomeMembro> [ = <exprTipo>];
}

Onde:
  • NomeClasse é o nome da classe;
  • acesso é o especificador de acesso opcional;
  • tipo é o nome do tipo deste membro;
  • nomeMembro é o nome deste membro; e
  • exprTipo é uma expressão que resulta num valor do tipo que pode inicializar o membro.
Os especificadores de acesso serão vistos na lição sobre encapsulamento.

Os membros de uma classe, no caso os seus atributos, podem armazenar valores para cada objeto existente. Desta maneira, para usar os atributos, devemos ter objetos.

Um objeto da classe Lampada pode ser criado com:

Lampada lamp1 = new Lampada();

O operador '.', denominado seletor, permite selecionar um membro de um objeto para ser utilizado. Assim podemos atribuir valores a atributos de um objeto com:

lamp1.voltagem = 110;
lamp1.potencia = 40;

De maneira análoga ao uso de variáveis, atributos de objetos podem ser usados em expressões ou exibidos para o usuário:

// Java ou C#
double corrente = lamp1.potencia / lamp1.voltagem;

// Java
System.out.println("Voltagem = " + lamp1.voltagem);
// C#
Console.write("Potencia = " + lamp1.potencia);

Outro objeto da classe Lampada pode ser criado com:

Lampada lamp2 = new Lampada();

E com ele podemos fazer:

lamp2.potencia = 100;
lamp2.voltagem = 220;

Ao atribuir valores aos campos do objeto lamp2, tais dados ficam armazenados na área de memória reservada para o objeto lamp2, não interferindo nos dados retidos pelo objeto lamp1. Pois cada objeto tem sua própria memória, permitindo guardar dados distintos dos demais objetos do seu tipo ou de outros.

Devemos observar que muitos objetos não podem ser descritos adequadamente apenas por seus atributos, requerendo também que suas ações, ou seja, suas capacidades sejam representadas. Uma classe incorpora o conceito de tipo abstrato de dados, definindo tanto conjunto de valores que representam este tipo como também o conjunto de operações sobre tais valores [10]. 

Por exemplo, um modelo de automóvel, para um simulador ou para um jogo, deve ser capaz de acelerar, frear e virar, ou seja, o modelo deve ser capaz de representar as ações dos objetos sob a forma de operações. Temos, portanto, que a modelagem de uma classe de objetos envolve a definição de atributos (que conterão os dados que descreverão sua aparência e estado -- esta lição) e de operações (que podem ser realizadas com tais atributos ou outros dados fornecidos -- a próxima lição), como ilustrado a seguir.


Cada classe define os atributos que seus objeto possuirão, de maneira que cada um destes possam ter valores distintos para cada um destes atributos, mesmo que tais valores sejam iguais (tal como dois objetos idênticos). A classe também define um conjunto único de operações para todos os objetos, ou seja, as ações realizadas serão as mesmas quanto à natureza, variando apenas em relação ao valor dos atributos de cada objeto. Esse é o assunto da próxima lição!

POO-F-03-Criação de Objetos POO-F-05-Métodos

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.

terça-feira, 16 de janeiro de 2018

POO::Fundamentos-03-Criação de Objetos

POO-F-02-Objetos, Tipos e Classes POO-F-04-Atributos
No mundo real, a existência de qualquer tipo de objeto exige que ele seja previamente criado ou fabricado, possibilitando que possa ser usado. A POO, como um modelo de programação que procura representar a realidade, também exige que os objetos sejam previamente criados para que possam ser utilizados.


Quando um livro é publicado, geralmente são impressos muitos exemplares, ou seja, objetos do mesmo tipo e também iguais em termos de suas características e conteúdo, mas fisicamente distintos. Cada exemplar de um livro específico é um objeto que tem "vida" própria, ou seja, passa a existir assim que sua produção é finalizada; pode ser armazenado, transportado, exibido e comercializado separadamente dos demais exemplares; pode ser usado e consultado; até mesmo descartado e destruído, quando este "ciclo de vida" se encerra, sem que o se passa com um exemplar, ocorra com outro.

Cada livro é um objeto que tem existência independente dos demais objetos, incluindo aqueles iguais (do mesmo tipo, com as mesmas características). Cada livro que existe é uma instância, um objeto distinto de um tipo específico.

Neste sentido, as classes são como modelos para a criação de objetos, pois definem as características (ou atributos) associadas a tais objetos, assim como seus comportamentos (ou operações). Assim, os objetos são sempre criados a partir de uma classe específica que serve como ponto de partida.

A criação de um objeto é, portanto, uma operação importante e especial, tanto que possui um operador próprio na maioria das linguagens de programação OO. No Java e no C# existe o operador new, responsável pela criação de novos objetos, uma operação denominada instanciação.

Além do operador new, é necessária a indicação da classe que originará o novo objeto, como na sintaxe abaixo:

<Classe> objeto = new <construtor>([lista_argumentos]);

Aqui:
  • Classe indica o nome da classe que determina o tipo do objeto;
  • objeto é a variável que "contém" o objeto;
  • new é o operador especial de instanciação;
  • construtor é uma operação especial de cada classe que determina como novos objetos serão criados; e
  • lista_argumentos opcionalmente fornece dados para a criação do novo objeto.
A classe Livro simples que segue, embora vazia, é sintaticamente válida:

class Livro {
}

Como a classe Livro existe, é possível criar novos objetos deste tipo com:

Livro livro1 = new Livro();
Livro livro2 = new Livro();

As variáveis podem ter qualquer nome válido, embora livro1 e livro2 são algo adequadas, pois ao mesmo tempo que dão uma pista sobre o tipo do objeto que contém, permitem diferenciá-los.

Os construtores, em Java e C#, como na maioria das linguagens OO, têm o mesmo nome que as respectivas classes, no caso a classe Livro. Também é comum que, quando nenhum construtor é fornecido pelo programador (nosso caso), o compilador adicione automaticamente um construtor para permitir a criação de objetos, o que se denomina construtor default, que não recebe argumentos. Assim, qualquer classe válida criada permite criar objetos.

Os construtores providenciam a alocação de espaço na memória para armazenamento do objeto, assim como sua inicialização adequada, conforme determinado pelo programador.

Após sua criação, os objetos podem ser utilizados, até que sejam destruídos, ou seja, removidos da memória. A destruição dos objetos ocorre em três situações: (i) por determinação do programador; (ii) porque o objeto sai fora do escopo onde foi criado; (iii) por conta do encerramento do programa.

Assim, as etapas de criação, de utilização e, por fim, de destruição, compõem o ciclo de vida dos objetos pode ser ilustrado como na figura que segue.


Outro aspecto importante é que nos sistemas de programação orientada a objeto, cada instância tem um identificador que permite diferenciar um objeto de outro, mesmo quando são iguais. Como um número de série de um produto qualquer, o número do chassi de um veículo ou o número MAC de uma placa de rede: identificadores únicos no universo das instâncias de um tipo de objeto específico. Aqui temos uma terceira característica importante da POO que é a identidade: cada objeto é único e deve ser univocamente identificado em sistema de programação orientada a objetos.

Com a criação de objetos, acabamos por discutir algumas questões fundamentais da OO:

  • Todo objeto é de um tipo específico/classe específica.
  • Os objetos da mesma classe, ou seja, do mesmo tipo, possuem as mesmas características (atributos) e comportamento (operações).
  • Todo objeto é criado é distinto dos demais, independente de seus tipos.
  • Todo objeto tem um ciclo de vida.
  • Todo objeto é único e pode ser distinguido dos demais.
Precisamos agora dotar nossas classes de características e comportamentos que permitam representar elementos da realidade e, com isso, tornar a técnica da OO útil na resolução de problemas. Atributos e operações são nossas próximas lições.

POO-F-02-Objetos, Tipos e Classes POO-F-04-Atributos


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.