S02 - Atividade de Aprendizagem: Abstração
Visão Geral
Nesta atividade, você aprenderá e praticará o princípio da Abstração.
Preparação
O que é Abstração?
Abstração é o processo de transformar ideias complexas em simples. É remover características de algo para que somente as essenciais permaneçam. Como programadores, criamos e usamos abstrações o tempo todo. Considere a seguinte linha de código Python.
print("Olá, mundo!")
Esteja você familiarizado ou não com Python, você provavelmente reconhece este como o primeiro programa que todos nós aprendemos a escrever. Você provavelmente também entende que esta instrução mostrará as palavras "Olá, mundo!" na tela do computador. No entanto, relativamente poucos programadores conhecem os detalhes de como isso é feito. Acontece que são necessárias mais de 3.000 linhas de C (conteúdo em inglês) para implementar essa função.
A função print em Python é uma abstração. É a simplificação de algo que na verdade é bastante complexo. Para usá-lo, tudo o que precisamos saber é o nome da função em si, ou "print", e os argumentos necessários, algum texto literal ou uma variável que pode ser transformada em texto literal.
A função print pode ser usada dentro de outra função que escrevemos para criar outra abstração. Ou seja, uma função diferente que simplifica uma ideia ou tarefa de ordem superior. Essa sobreposição de abstrações é exatamente como todos os softwares usados no mundo hoje, desde editores de texto como o Bloco de Notas até plataformas de mídia social como o Facebook, são escritos.
Objetos e Classes
Programar com classes é outra maneira de criar abstrações em software. Começamos, no entanto, pensando em objetos. Um objeto é um modelo conceitual para uma categoria de coisas, reais ou imaginárias, que tem uma responsabilidade específica dentro do nosso programa. Por exemplo, podemos pensar em um objeto que contém e fornece informações de identificação sobre uma pessoa.
Objetos têm estado e comportamento que lhes permitem cumprir sua responsabilidade. O objeto pessoa pode ter estados como “primeiro nome" e "sobrenome". Ele também pode ter comportamentos relacionados, como “obter nome informal" e “obter nome formal". A seguir está uma representação gráfica do nosso objeto pessoa.
Pensar em uma pessoa dessa maneira é uma abstração. É uma simplificação de algo que é mais complexo. Pode parecer trivial no começo, mas não ter que se preocupar com diferenças na forma lexical dos nomes em nenhum outro lugar do nosso software é significativo. Podemos simplesmente confiar que o objeto pessoa cuidará disso.
Também não há comportamentos não relacionados. Certamente há muitos outros que poderíamos pensar como pertencentes a uma pessoa. Entretanto, nossa conceituação contém apenas aqueles que ajudam a cumprir sua responsabilidade específica.
Com um objeto em mente, estamos prontos para traduzi-lo para um modelo de código chamado classe. O estado do objeto é traduzido em variáveis chamadas atributos. Os comportamentos do objeto são traduzidos em funções chamadas métodos.
Como ponto de partida para escrever o código real, podemos criar um Diagrama de Classes, que é uma caixa que tem o nome da classe na parte superior, as variáveis membro ou atributos no meio e os métodos na parte inferior. Variáveis membro são seguidas por dois pontos e seu tipo de dados, e métodos são seguidos por dois pontos e seu tipo de retorno. A seguir está um exemplo do Diagrama de Classes para a classe Pessoa descrita acima:
Você pode observar no diagrama de classes que o padrão em C# é usar TitleCase (caixa de títulos) para nomes de classes e métodos, e usar _underscoreCamelCase para nomes de variáveis membro. O sublinhado no início ajuda você a reconhecer que as variáveis são membros da classe e são diferentes das variáveis locais regulares.
Depois que tivermos um diagrama de classes, teremos detalhes suficientes para começar a implementar a classe no código. A seguir está um exemplo da Pessoa traduzida para uma classe em código. Não deixe de ler todos os comentários e códigos com atenção.
// Um modelo de código para a categoria de coisas conhecida como Pessoa. A
// responsabilidade de Pessoa é manter e exibir informações pessoais.
public class Pessoa
{
// A convenção C# é iniciar variáveis membro com um sublinhado _
public string _primeiroNome = "";
public string _sobrenome = "";
// Um método especial, chamado construtor, que é invocado usando a
// nova palavra-chave seguida pelo nome da classe e parênteses.
public Pessoa()
{
}
// Um método que exibe o nome da pessoa no formato
// <primeiro nome e sobrenome>.
public void ObterNomeSobrenome()
{
Console.WriteLine($"{_primeiroNome} {_sobrenome}");
}
// Um método que exibe o nome da pessoa no formato
// <sobrenome, primeiro nome>.
public void ObterSobrenomeNome()
{
Console.WriteLine($"{_sobrenome}, {_primeiroNome}");
}
}
Uma Nota sobre a Criação de Diagramas
Existem muitas ferramentas para ajudar você a criar diagramas de classes e, à medida que você aprende mais detalhes sobre programação com classes, você descobrirá que há muitos elementos que podem ser adicionados a um diagrama.
Para este curso, a parte importante é focar no nome, nos atributos (variáveis membro) e nos comportamentos (métodos). Se você quiser encontrar e usar uma ferramenta para criar os diagramas, isso é ótimo, mas você também pode fazer isso de forma tão simples quanto criar uma lista com marcadores em qualquer editor de texto:
Classe: Pessoa
Atributos:
* _primeiroNome : string
* _sobrenome : string
Comportamentos:
* ObterNomeSobrenome() : void
* ObterSobrenomeNome() : void
Classes e Instâncias
Uma classe por si só é apenas um modelo para algo. Ele só se torna útil quando uma instância é criada e atribuída a uma variável no seu programa. Uma instância é a concretização de atributos e métodos na memória do computador.
Outra maneira de pensar em criar uma instância de uma classe é imaginar que você está assando um bolo. Nessa metáfora, uma classe é como uma receita. É um modelo para um bolo, mas não o produto assado propriamente dito. Em contraste, uma instância é o que sai do forno. É a concretização dos detalhes da receita numa camada crocante e docinha! Considere o seguinte código.
Pessoa pessoa = new Pessoa();
pessoa._primeiroNome = "Joseph";
pessoa._sobrenome = "Smith";
pessoa.ObterNomeSobrenome();
pessoa.ObterSobrenomeNome();
Saída:
Joseph Smith
Smith, Joseph
Neste exemplo, uma instância da classe Pessoa é criada e atribuída à variável chamada "pessoa". Ela é criada invocando um método especial, chamado construtor, que é o nome da classe seguido por parênteses. Algumas linguagens de programação, como Java, C# e outras, exigem a palavra-chave "new" ao chamar um construtor. (Você aprenderá mais sobre construtores nas próximas aulas.)
Um dos aspectos mais importantes da programação com classes é que várias instâncias podem ser criadas e usadas no mesmo programa. O exemplo a seguir mostra a criação de duas instâncias de Pessoa. Observe como os atributos "primeiro nome" recebem valores diferentes, variando o comportamento do método "obter nome sobrenome" de uma instância para outra.
Pessoa pessoa1 = new Pessoa();
pessoa1._primeiroNome = "Emma";
pessoa1._sobrenome = "Smith";
pessoa1.ObterNomeSobrenome();
Pessoa pessoa2 = new Pessoa();
pessoa2._primeiroNome = "Joseph";
pessoa2._sobrenome = "Smith";
pessoa2.ObterNomeSobrenome();
Saída:
Emma Smith
Joseph Smith
Tipos de Dados Personalizados
Ao criar classes, você está na verdade criando um novo tipo de dado personalizado. Por exemplo, em C#, há tipos de dados integrados para inteiros e strings. Quando você declara uma variável desses tipos, é como criar uma caixa que pode conter esse tipo de dado e colocar um rótulo na parte externa da caixa com o nome da variável. Por exemplo, considere o seguinte código:
int altura;
string cor;
Este código cria caixas que você pode preencher posteriormente com valores. Então, quando você atribui o valor, ele o coloca na caixa. Por exemplo, o código a seguir coloca o número 17 na caixa:
altura = 17;
E esse código, muda para 24:
altura = 24;
Agora, suponha que você trabalhasse para uma empresa de revestimentos de janelas que fabricasse persianas e quisesse um programa que trabalhasse com essas persianas. Seu código precisaria armazenar a largura, altura e cor de cada persiana no programa.
Imagine como seria ótimo se o C# tivesse um tipo de dado como string ou int, mas que
fosse projetado especificamente com persianas em mente e que pudesse armazenar todos esses três componentes
juntos. Com classes, podemos criar esse novo tipo de dado da seguinte maneira:
public class Persiana
{
public double _largura;
public double _altura;
public string _cor;
}
Com esse novo tipo de dado personalizado, agora você pode criar uma nova variável cujo tipo é
Persiana. Podemos pensar nisso como criar uma nova caixa para isso. A diferença é que esta caixa tem
um pequeno separador dentro dela, criando três compartimentos menores, um para cada uma das três variáveis membro.
Toda vez que você cria uma nova variável Persiana, ela cria outra caixa grande que tem esses três componentes. A classe é o que define a estrutura da caixa grande, e cada uma dessas caixas grandes que você cria é um objeto ou instância dessa classe.
Por exemplo:
Persiana cozinha = new Persiana();
No seu código, sempre que você usa a variável cozinha, ela se refere a uma caixa grande e, se você
quiser se referir a qualquer coisa dentro da caixa, use o operador "ponto". Você pode definir os valores da
seguinte forma:
cozinha._largura = 60;
cozinha._altura = 48;
cozinha._cor = "branco";
E você pode acessar os valores da mesma maneira:
Console.WriteLine(cozinha._largura);
Armazenar todos esses valores juntos em uma caixa maior pode simplificar muito seu código, porque, por exemplo, agora você pode passar toda essa caixa grande para uma função como um parâmetro ou retorná-la.
Adicionando Comportamentos
Além de armazenar valores juntos que pertencem um ao outro, você também pode colocar as funções membro, ou métodos, que usam esses dados na caixa com eles.
Por exemplo, continuando com o mesmo exemplo de persiana, suponha que você quisesse um método para calcular a
área da persiana, para que o programa pudesse exibir a quantidade de material necessária. Isso poderia ser
adicionado como uma função dentro da caixa, chamada ObterArea().
public class Persiana
{
public double _largura;
public double _altura;
public string _cor;
public double ObterArea()
{
return _largura * _altura;
}
}
A função ObterArea() agora também fica dentro da caixa. E como está dentro da caixa, você usa a
notação de "ponto" para chamá-la:
double valorMaterial = cozinha.ObterArea();
(As linhas vermelhas na imagem indicam que há linhas de código associadas a esse método.)
A seguir mostramos todas essas linhas juntas:
Persiana cozinha = new Persiana();
cozinha._largura = 60;
cozinha._altura = 48;
cozinha._cor = "Branco";
double valorMaterial = cozinha.ObterArea();
Observe que para chamar o método ObterArea(), você deve fornecer primeiro o nome do objeto, seguido
de um ponto. Então, observe também que você não precisa passar variáveis para largura e altura.
Isso ocorre porque esse método é um membro da classe, portanto ele já está "na caixa" e pode acessar qualquer
variável membro que precisar.
O fato de o método acessar as variáveis membro em sua caixa também é o motivo pelo qual ObterArea()
retornará valores diferentes para diferentes objetos Persiana, como cozinha.ObterArea()
e sala.ObterArea(), porque esses dois objetos terão valores diferentes para suas larguras e alturas.
Objetos Dentro de Objetos
No exemplo anterior, todas as variáveis membro tinham tipos simples (double ou string). Mas variáveis membro também podem ter tipos personalizados. Isso é semelhante a colocar outra caixa dentro da maior.
Por exemplo, no caso das persianas, você pode considerar criar outra classe para a casa inteira. Internamente, ele pode conter outras variáveis membro, algumas das quais podem ser tipos simples e outras podem ser tipos complexos. Por exemplo:
public class Casa
{
public string _proprietario;
public Persiana _cozinha;
public Persiana _sala;
}
Lembre-se de que você deve inicializar as persianas com novos valores. Você pode fazer isso depois de criar um
novo objeto Casa:
Casa casaJoao = new Casa();
casaJoao._cozinha = new Persiana();
casaJoao._sala = new Persiana();
Ou você pode inicializar essas variáveis diretamente na definição da classe:
public class Casa
{
public string _proprietario = "";
public Persiana _cozinha = new Persiana();
public Persiana _sala = new Persiana();
}
Depois de criar um novo objeto Casa, você pode acessar suas variáveis membro usando o operador
"ponto", como antes:
Casa casaJoao = new Casa();
casaJoao._proprietario = "Família do João";
Quando você quiser acessar os valores internos de uma dessas variáveis membro do tipo complexo, você pode simplesmente encadear várias operações de "ponto", como:
casaJoao._cozinha._largura = 60;
Listas de Tipos Personalizados
Da mesma forma que você pode criar uma lista de strings ou uma lista de doubles, você também pode criar uma lista
de um novo tipo personalizado. Por exemplo, em vez de a classe Casa conter variáveis para as
persianas da cozinha e da sala, ela pode ter uma lista de persianas:
public class Casa
{
public string _proprietario;
public List<Persiana> _persianas = new List<Persiana>();
}
Com esta nova versão da classe Casa, você pode escrever código como:
casaJoao._persianas.Add(cozinha);
ou:
double valor = casaJoao._persianas[0].ObterArea();
ou:
foreach (Persiana p in casaJoao._persianas)
{
double valor = p.ObterArea();
}
A figura a seguir mostra como você pode pensar sobre o armazenamento do objeto casaJoao. Observe que
a variável membro _persianas agora se refere a uma lista de objetos Persiana. Cada
objeto Persiana pode ser referenciado por seu índice e cada um tem seus próprios valores para as
variáveis _largura, _altura e _cor.
Resumo
Pense nos parágrafos iniciais deste artigo. Um dos aspectos mais interessantes da abstração é que podemos sobrepô-las, como você acabou de ver, colocando objetos dentro de objetos. Cada um desses objetos pode ter sua própria responsabilidade, comportamento e estado.
Abstração é o primeiro princípio da programação com classes. À medida que você for melhorando na aplicação, perceberá que será capaz de pensar e falar sobre seu software, com programadores e não programadores, de uma forma muito natural. Por favor, não subestime o valor dessa habilidade. Ser capaz de comunicar claramente sobre o que é necessário e o que você está fazendo é essencial para criar um software pronto para mudanças.
Termos e Definições
Classe – Um novo tipo de dado personalizado que define atributos (variáveis membro) e métodos. É como um projeto para criar instâncias ou objetos dessa classe. Exemplo: Uma pessoa tem nome e sobrenome.
Instância – Uma variável cujo tipo de dado é a classe. Muitas vezes usamos o termo Objeto de forma intercambiável. Exemplo: Podemos ter duas instâncias da classe Pessoa: uma para Joao e uma para Maria.
Instanciar – Um verbo que significa "criar uma instância de". Exemplo: Podemos instanciar a classe Pessoa para criar um novo objeto.
Método – Uma função membro. Os métodos são chamados usando "notação de ponto" com uma
instância da classe antes do ponto. Exemplo: pessoa1.ObterNomeSobrenome()
Instruções da Atividade
Pratique o princípio da abstração criando classes para representar um currículo ou um histórico de empregos de uma pessoa como você pode ver no LinkedIn.
Projetar as Classes
Seu programa deve conter duas classes, uma para o Emprego e outra para o Currículo em si, da seguinte forma:
Classe: Emprego
- Responsabilidades:
- Mantém registro da empresa, cargo, ano de início e ano de término.
- Comportamentos:
- Exibe as informações do emprego no formato "
Cargo (Empresa) AnoInicio-AnoFim", por exemplo: "Engenheiro de Software (Microsoft) 2019-2022".
Classe: Currículo
- Responsabilidades:
- Mantém o registro do nome da pessoa e uma lista de seus empregos.
- Comportamentos:
- Exibe o currículo, que mostra primeiro o nome, seguido pela exibição de cada um dos empregos.
Com base nessas descrições, você pode criar diagramas de classes como o seguinte:
Inicie o Projeto
- Abra o projeto da classe no VS Code.
- Navegue até o projeto
Curriculona pastasemana02. Encontre o arquivoProgram.cs, que será seu ponto de entrada para o programa. - Verifique se você consegue executar o projeto e veja a saída inicial “Olá, mundo!".
Crie a classe Emprego
- Crie um novo arquivo para sua classe Emprego. Por convenção, isso deve ser chamado de
Emprego.cs. - Crie a classe (Dica: esta é a sintaxe da
public class Emprego). - Crie variáveis membro na classe para cada elemento que esta classe deve conter. Por convenção, essas variáveis
membro devem começar com um sublinhado e uma letra minúscula, como
_cargo.
Teste sua Classe Emprego
- De volta ao seu arquivo Program.cs, adicione o código à sua função
Main. - Crie uma nova instância da classe Emprego chamada
emprego1. - Defina as variáveis membro usando a notação de ponto (por exemplo,
emprego1._cargo = "Engenheiro de Software";) - Verifique se você pode exibir a empresa deste trabalho na tela, novamente usando a notação de ponto para acessar a variável membro.
- Crie um segundo emprego, defina suas variáveis e exiba esta empresa na tela também.
Exemplo de Resultado
Microsoft
Apple
Adicione um Método Exibir à Classe Emprego
- Retorne ao seu arquivo
Emprego.cse adicione um método (função membro) para exibir os detalhes do emprego. Este método não deve ter nenhum parâmetro e não precisa retornar nada. Por convenção, esse método deve começar com uma letra maiúscula, comoExibir, e se você tiver várias palavras, cada palavra deve ser maiúscula, comoExibirDetalhesEmprego. - Este método deve exibir os detalhes do emprego na tela no formato correto. Lembre-se de que o método pode acessar as variáveis membro diretamente, sem precisar que elas sejam passadas para ele.
- Retorne ao seu arquivo
Program.cs. Remova as linhas de código onde você exibiu a empresa anteriormente e substitua-as por chamadas para seu novo método. Lembre-se de chamar o método usando a mesma notação de ponto, comoemprego1.Exibir();.
Exemplo de Resultado
Engenheiro de Software (Microsoft) 2019-2022
Gerente (Apple) 2022-2023
Crie a Classe Curriculo
- Crie um novo arquivo para sua classe
Curriculo. Cada classe deve estar em seu próprio arquivo e o nome do arquivo deve corresponder ao nome da classe. - Crie a classe
Curriculo. - Crie a variável membro para o nome da pessoa.
- Crie a variável membro para a lista de empregos. (Dica: o tipo de dado dela deve ser
List<Emprego>, e provavelmente é mais fácil inicializá-la como uma nova lista logo quando você a declara.)
Teste sua Classe Curriculo
- Retorne ao seu
Program.cs. Adicione o final da funçãoMaine crie uma nova instância da classeCurriculo. - Adicione os dois empregos que você criou anteriormente à lista de empregos no objeto de currículo.
- Verifique se você pode acessar e exibir o primeiro cargo usando a notação de ponto semelhante a
meuCurriculo._empregos[0]._cargo.
Adicione um Método Exibir à Classe Curriculo
- Retorne à sua classe
Curriculoe adicione um método para exibir seus detalhes. - Este método não deve ter nenhum parâmetro e não deve retornar nada.
- No corpo do método, você deve exibir o nome da pessoa e, em seguida, percorrer por cada instância de
Empregona lista de empregos e exibi-los. - Dica: lembre-se de que você pode chamar o método
Exibirde cada emprego que você criou anteriormente. - Retorne à sua função principal, remova qualquer código que esteja exibindo informações e, em vez disso,
adicione uma chamada no final ao método
Exibirda sua classeCurriculopara exibir o nome e todos os empregos em uma linha.
Exemplo de Resultado
Nome: José Carlos
Empregos:
Engenheiro de Software (Microsoft) 2019-2022
Gerente (Apple) 2022-2023
Exemplo de Solução
Quando terminar, por favor, compare sua abordagem com o seguinte exemplo de solução (você também pode usar esse exemplo de solução como um guia se precisar de ajuda para terminar).
Envio
- Verifique se seu programa funciona conforme descrito.
- Faça commit e envie seu código para seu repositório GitHub.
- Verifique se você consegue ver seu código atualizado no GitHub.
- Responda ao questionário do Canvas para relatar seu trabalho.