O Paradigma Orientado a Objetos

No post anterior, vimos sobre um tipo de abstração que eu chamo de abstração de algoritmos, ou seja, dentro do seu algoritmo principal há trechos de outros algoritmos. Vimos, também, que essas abstrações podem ser encapsuladas em funções e que isso melhora a:

  • Expressividade no momento de escrever os algoritmos.
  • Clareza no entendimento dos mesmos.
  • Capacidade de reuso de suas partes.
  • Flexibilidade em alterá-los.

Antes de continuarmos com o Paradigma Orientado a Objetos, vamos ver um outro tipo de abstração. Para isso, considere o problema de escrever um algoritmo que lê o nome e as duas notas de um aluno e verifica se ele passou ou não, mostrando uma mensagem nesse modelo: “O aluno aluno passou com média média“. Podemos chegar ao seguinte programa:

[wptabs]
[wptabtitle]C#[/wptabtitle]
[wptabcontent][code language=”csharp” highlight=”4,7,10,12,15,24,27,29,34,38″]public static void Main(string[] args)
{
Console.Write("Nome: ");
String nome = Console.ReadLine();

Console.Write("P1: ");
double p1 = double.Parse(Console.ReadLine());

Console.Write("P2: ");
double p2 = double.Parse(Console.ReadLine());

double media = calculaMedia(p1, p2);

String passou;
if (verificaSePassou(p1, p2))
{
passou = "passou";
}
else
{
passou = "não passou";
}

Console.WriteLine("O aluno {0} {1} com média {2:F1}.\n", nome, passou, media);
}

public static double calculaMedia(double p1, double p2)
{
double media = (p1 + p2) / 2;

return media;
}

public static bool verificaSePassou(double p1, double p2)
{
bool passou;

double media = calculaMedia(p1, p2);

if (media >= 7)
{
passou = true;
}
else
{
passou = false;
}

return passou;
}
[/code][/wptabcontent]
[wptabtitle]Java[/wptabtitle]
[wptabcontent][code language=”java” highlight=”5,8,11,13,16,22,25,26,31,34″]
public static void main(String[] args) {
Scanner console = new Scanner(System.in);

System.out.print("Nome: ");
String nome = console.nextLine();

System.out.print("P1: ");
double p1 = Double.parseDouble(console.nextLine());

System.out.print("P2: ");
double p2 = Double.parseDouble(console.nextLine());

double media = calculaMedia(p1, p2);

String passou;
if (verificaSePassou(p1, p2)) {
passou = "passou";
} else {
passou = "não passou";
}

System.out.printf("O aluno %s %s com média %.1f.\n", nome, passou, media);
}

public static double calculaMedia(double p1, double p2) {
double media = (p1 + p2) / 2;

return media;
}

public static boolean verificaSePassou(double p1, double p2) {
boolean passou;

double media = calculaMedia(p1, p2);

if (media >= 7) {
passou = true;
} else {
passou = false;
}

return passou;
}
[/code][/wptabcontent]
[/wptabs]

Abstrações, Encapsulamento e Dados Estruturados

Aqui podemos perceber, pelas linhas destacadas, que os dados do aluno estão espalhados pelos algoritmos. Isso nos leva às seguintes observações:

  • Para eu escrever os algoritmos do cálculo da média e da verificação se o aluno passou ou não, tive que passar os dados do aluno um a um, ou seja, foi difícil (impossível) de expressar que o que eu realmente queria era calcular a média do aluno e não de dois números (a média do aluno pode ter uma fórmula qualquer); ou que eu queria verificar se o aluno passou e não se dois números passaram. Além disso, não foi possível reusar a informação, tendo que escrever o mesmo conjunto de variáveis em locais diferentes.
  • De forma semelhante, se eu fosse tentar entender o algoritmo como um todo, teria que notar que existe um conjunto de dados que se repetem de alguma forma; e, analisando melhor, veria que esses dados, na verdade, representam um aluno.
  • Se, por algum motivo, eu precisar alterar os dados do aluno (acrescentando, por exemplo, mais uma nota) eu teria que fazer modificações em vários locais distintos (o maior problema aqui, talvez seria ter que alterar o número de parâmetros das funções, bem como a passagem dos argumentos).

Diante dessas observações, dizemos que as linhas destacadas são abstrações para o que chamamos de aluno na vida real. Assim, o que nos falta nesse momento é um mecanismo para isolar essa abstração de dentro do nosso algoritmo. A maioria das linguagens de programação (nada a ver com os paradigmas em si) nos fornece esse mecanismo: ele é chamado de dado estruturado. Ou seja, podemos dizer que dado estruturado é um mecanismo para encapsular uma abstração. Nesse caso, é o que eu chamo de abstração de dados.

Agora que conhecemos que existe esse mecanismo, podemos resolver o nosso problema da seguinte forma:

[wptabs]
[wptabtitle]C#[/wptabtitle][wptabcontent][code language=”csharp” highlight=”5,8,11,14,16,19,28,31,33,38,42,57,58,59,60,61″] public class Program
{
public static void Main(string[] args)
{
Aluno aluno = new Aluno();

Console.Write("Nome: ");
aluno.nome = Console.ReadLine();

Console.Write("P1: ");
aluno.p1 = double.Parse(Console.ReadLine());

Console.Write("P2: ");
aluno.p2 = double.Parse(Console.ReadLine());

double media = calculaMedia(aluno);

String passou;
if (verificaSePassou(aluno))
{
passou = "passou";
}
else
{
passou = "não passou";
}

Console.WriteLine("O aluno {0} {1} com média {2:F1}.\n", aluno.nome, passou, media);
}

public static double calculaMedia(Aluno aluno)
{
double media = (aluno.p1 + aluno.p2) / 2;

return media;
}

public static bool verificaSePassou(Aluno aluno)
{
bool passou;

double media = calculaMedia(aluno);

if (media >= 7)
{
passou = true;
}
else
{
passou = false;
}

return passou;
}
}

class Aluno
{
public string nome;
public double p1, p2;
}
[/code][/wptabcontent]
[wptabtitle]Java[/wptabtitle][wptabcontent][code language=”java” highlight=”6,9,12,15,17,20,26,29,30,35,38,50,51,52,53,54,55″]public class Program {

public static void main(String[] args) {
Scanner console = new Scanner(System.in);

Aluno aluno = new Aluno();

System.out.print("Nome: ");
aluno.nome = console.nextLine();

System.out.print("P1: ");
aluno.p1 = Double.parseDouble(console.nextLine());

System.out.print("P2: ");
aluno.p2 = Double.parseDouble(console.nextLine());

double media = calculaMedia(aluno);

String passou;
if (verificaSePassou(aluno)) {
passou = "passou";
} else {
passou = "não passou";
}

System.out.printf("O aluno %s %s com média %.1f.\n", aluno.nome, passou, media);
}

public static double calculaMedia(Aluno aluno) {
double media = (aluno.p1 + aluno.p2) / 2;

return media;
}

public static boolean verificaSePassou(Aluno aluno) {
boolean passou;

double media = calculaMedia(aluno);

if (media >= 7) {
passou = true;
} else {
passou = false;
}

return passou;
}
}

class Aluno {

public String nome;
public double p1, p2;

}
[/code][/wptabcontent]
[/wptabs]

Detalhes sobre Dados Estruturados

Podemos notar, na primeira linha destacada, a criação de um variável chamada aluno cujo tipo é Aluno. Mas, de onde vem esse novo tipo? Mais abaixo, nas últimas linhas destacadas, está a definição desse novo tipo, que é feita usando-se a palavra reservada class. Observações importantíssimas:

  • A palavra reservada class, na verdade, é usada para criar uma classe, que é muito mais que um simples dado estruturado (iremos ver isso mais à frente neste mesmo post). Em algumas linguagens de programação, os dados estruturados são criados por meio de palavras reservadas como struct, record, entre outras.
  • Após a criação da variável aluno precisamos atribuir a ela um valor que também seja do tipo Aluno. No nosso caso, a forma de fazer isso é por meio do comando new que tem a seguinte estrutura:
    • new
    • tipo do qual que queremos criar um novo valor
    • ()

    Assim, para criar um novo valor do tipo Aluno, o comando é new Aluno().

Uma outra forma de enxergar os dados estruturados é fazendo um paralelo com vetores. Relembrando, vetor é um conjunto de variáveis:

  • Do mesmo tipo.
  • Tamanho fixo.
  • Indexado.

Assim, podemos definir dado estruturado como sendo um conjunto de variáveis:

  • De diferentes tipos.
  • Tamanho fixo.
  • Indexado.

Não é à toa que, na maior parte dos livros sobre algoritmos e linguagens de programação, vetor também é chamado de estrutura homogênea e dado estruturado também é chamado de estrutura heterogênea.

Voltando ao paralelo entre vetores e dados estruturados, o que muda entre um e outro, além dos tipos que eles comportam, é a forma de indexação. Nos vetores, a indexação se dá por meio de números; já em um dado estruturado, a indexação se dá por meio do nome da variável que se quer acessar.

Em forma de código, teríamos algo assim:

[code language=”csharp”]
// Criação de um novo vetor de 10 inteiros:
int[] v = new int[10];
// Acesso à 5ª variável (índices começam em zero):
int umValor = v[4];

// Criação de um novo aluno:
Aluno a = new Aluno();
// Acesso à variável nome:
string outroValor = a.nome;
[/code]

Ou, em forma de imagem, podemos enxergar desse jeito:

Estruturas homogêneas vs heterogêneas.

Estruturas homogêneas vs heterogêneas.

E, para resumir abstrações e encapsulamento:

Encapsulamento é um mecanismo que nos permite isolar abstrações. Se forem abstrações de algoritmos, chamamos o encapsulamento de função. Se forem abstrações de dados, chamamos o encapsulamento de dados estruturados.

Finalmente, a Orientação a Objetos

Nesse momento, o que podemos perceber é que temos abstrações de algoritmos de um lado e abstrações de dados do outro. Essas abstrações estão separadas! Com isso a nossa forma de pensar (o nosso paradigma) tem o seguinte formato:

resultado = funcao(dado, dadosExtras)

Porém, muitas vezes, pode existir um conjunto de algoritmos que trabalha sempre com os mesmos dados. Seria interessante se pudéssemos encapsulá-los em uma única abstração! É aí que entra o Paradigma Orientado a Objetos: ele nos fornece esse mecanismo: ele é chamado de classe. Uma classe representa um conjunto de elementos de um mesmo tipo. E, cada um desses elementos de uma mesma classe é chamado de objeto. Quando usamos esse paradigma, nós passamos a “conversar” com esses objetos (enviar mensagens para eles) e nossa forma de pensar passa a ter o seguinte formato:

resultado = dado.funcao(dadosExtras)
ou
resultado = objeto.mensagem(dadosExtras)

Os detalhes de como se faz a construção de classes, a utilização de objetos e de todos os mecanismos que eles propiciam serão vistos mais para frente. Mas, por enquanto, vamos ver como ficaria o programa que nos serviu de exemplo até agora:

[wptabs]
[wptabtitle]C#[/wptabtitle][wptabcontent][code language=”csharp” highlight=”16,19″] public class Program
{
public static void Main(string[] args)
{
Aluno aluno = new Aluno();

Console.Write("Nome: ");
aluno.nome = Console.ReadLine();

Console.Write("P1: ");
aluno.p1 = double.Parse(Console.ReadLine());

Console.Write("P2: ");
aluno.p2 = double.Parse(Console.ReadLine());

double media = aluno.calculaMedia();

String passou;
if (aluno.verificaSePassou())
{
passou = "passou";
}
else
{
passou = "não passou";
}

Console.WriteLine("O aluno {0} {1} com média {2:F1}.\n", aluno.nome, passou, media);
}
}

class Aluno
{
public string nome;
public double p1, p2;

public double calculaMedia()
{
double media = (p1 + p2) / 2;

return media;
}

public bool verificaSePassou()
{
bool passou;

double media = calculaMedia();

if (media >= 7)
{
passou = true;
}
else
{
passou = false;
}

return passou;
}
}
[/code][/wptabcontent]
[wptabtitle]Java[/wptabtitle][wptabcontent][code language=”java” highlight=”17,20″]public class Program {

public static void main(String[] args) {
Scanner console = new Scanner(System.in);

Aluno aluno = new Aluno();

System.out.print("Nome: ");
aluno.nome = console.nextLine();

System.out.print("P1: ");
aluno.p1 = Double.parseDouble(console.nextLine());

System.out.print("P2: ");
aluno.p2 = Double.parseDouble(console.nextLine());

double media = aluno.calculaMedia();

String passou;
if (aluno.verificaSePassou()) {
passou = "passou";
} else {
passou = "não passou";
}

System.out.printf("O aluno %s %s com média %.1f.\n", aluno.nome, passou, media);
}
}

class Aluno {

public String nome;
public double p1, p2;

public double calculaMedia() {
double media = (p1 + p2) / 2;

return media;
}

public boolean verificaSePassou() {
boolean passou;

double media = calculaMedia();

if (media >= 7) {
passou = true;
} else {
passou = false;
}

return passou;
}

}
[/code][/wptabcontent]
[/wptabs]

Nas linhas destacadas podemos ver esse novo formato, essa nova forma de pensar, esse novo paradigma.

Além disso, podemos ver outras mudanças:

  • As funções saíram do algoritmo principal e foram encapsuladas junto dos dados.
  • A palavra reservada static foi retirada das funções.
  • Os dados do aluno não precisam mais serem passados como argumentos para as funções, pois eles já estão encapsulados juntos na mesma abstração.

Não se preocupe muito em entender agora como tudo isso funciona. Como comentei anteriormente, os detalhes serão vistos mais para frente.

Conclusão

Agora, ao analisarmos o nosso programa, fica claro que, ao usarmos classes e objetos, ganhamos um pouco mais em:

  • Expressividade no momento de escrever os algoritmos.
  • Clareza no entendimento dos mesmos.
  • Capacidade de reuso de suas partes.
  • Flexibilidade em alterá-los.

E a forma de pensar, de construir um algoritmo, muda em relação ao Paradigma Procedimental: agora, o nosso algoritmo passa a ser formado por um conjunto de objetos que “conversam” e colaboram entre si para resolver o problema. A figura abaixo ilustra essa situação:

Paradigma Orientado a Objetos

O algoritmo passa a ser formado por um conjunto de objetos que “conversam” e colaboram entre si para resolver o problema.

Agora, passamos a ter que pensar em como dividir nosso problema entre objetos capazes de, cada um por sua vez, resolver problemas menores. Dividir para conquistar continua sendo o lema, mas, agora, é como se tivéssemos uma horda de Minions para os quais pudéssemos passar tarefas que, quando completadas, solucionam o problema que queremos resolver.

Horda de Minions

Horda de Minions.

Minions Trabalhando

Minions Trabalhando.

Após passar pela leitura de todos esses paradigmas, deixo um exercício onde você mesmo fará a transição por todos eles. A resposta, bem como os programas desse post, encontram-se em:


Antes de finalizar de fato esse post, quero fazer uma observação: nos programas usados aqui eu coloquei o seguinte trecho de algoritmo:

[code language=”csharp”]
if (media >= 7) {
passou = true;
} else {
passou = false;
}
[/code]

Os alunos mais atentos devem ter percebido que isso poderia ser substituído por:

[code language=”csharp”]
passou = media >= 7;
[/code]

Se você conseguiu enxergar isso, parabéns! 🙂

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *