S03 - Atividade de Aprendizagem: Encapsulamento
Visão Geral
Nesta atividade, você aprenderá e praticará o princípio do Encapsulamento.
Preparação
Encapsulamento é o ato de envolver algo, como se estivesse em uma cápsula. Isso significa pensar cuidadosamente sobre os comportamentos que suas classes precisam e então esconder os detalhes de como elas executam esses comportamentos, até mesmo fazendo com que outros códigos não possam ver ou manipular esses detalhes.
Anteriormente, você aprendeu sobre o poder e o valor da abstração na escrita de programas que podem lidar com mudanças. Um dos princípios fundamentais é deixar que uma classe seja responsável pelos detalhes de uma tarefa específica. Então, as outras partes do programa não precisam se preocupar com esses detalhes, desde que saibam como interagir com a classe em si.
Com o encapsulamento, levamos essa ideia ainda mais longe, impedindo que outras partes do programa acessem esses detalhes. A ideia é que uma classe encapsule completamente, ou contenha, todos os dados e lógica que envolvem a tarefa. Se outras partes do programa precisarem executar a tarefa ou algo relacionado a ela, elas não devem tentar acessar os dados diretamente. Em vez disso, devem se comunicar com a classe em questão, e ela pode acessar os dados conforme necessário. Se a classe tiver métodos bem projetados, outras partes do programa não precisarão de acesso direto aos dados internos. Elas podem confiar nos métodos fornecidos.
Dessa forma, um dos principais componentes do encapsulamento na programação envolve a ocultação de informações ou o controle do acesso a dados internos.
Por Que o Encapsulamento É Importante
À primeira vista, pode parecer desnecessário esconder informações ou restringir o acesso a partes do código de outros lugares do programa, mas isso acaba sendo crítico por dois motivos principais:
- Se algo estiver quebrado, qualquer código que tenha acesso pode ser o culpado.
- Se algo precisa mudar, qualquer código que tenha acesso pode precisar mudar.
Considere o seguinte código.
public class Conta
{
public int _saldo = 0;
public void Deposito(int valor)
{
_saldo = _saldo + valor;
}
}
Conta conta = new Conta();
conta._saldo = 50;
conta.Depositar(100);
O que aconteceria se decidíssemos alterar o atributo saldo na classe Conta para uma lista de transações? Por um lado, seríamos capazes de monitorar depósitos individuais. Por outro lado, quebraríamos uma parte diferente do programa.
public class Conta
{
public List<int> _transacoes = new List<int>(); // se mudarmos isso para uma lista...
public void Depositar(int valor)
{
_transacoes.Add(valor);
}
}
Conta conta = new Conta();
conta._saldo = 50; // se fizermos a alteração acima, esse código não funcionará mais!
conta.Depositar(100); // no entanto, esta abordagem continuaria a funcionar
Nesse caso, os detalhes da classe Conta não estão bem encapsulados. Precisamos de uma maneira de esconder os atributos de classe para que outro código não tente alterá-los diretamente. Se pudermos, isso nos permitirá minimizar as interdependências entre diferentes partes do nosso código e protegê-las de alterações drásticas.
Exemplo em Vídeo
Por favor, assista ao vídeo a seguir que destaca esse exemplo de encapsulamento.
Link Direto: ▶️Encapsulamento (7 minutos)
Usando Modificadores de Acesso
Algumas linguagens de programação, como C#, usam modificadores de acesso, ou palavras-chave especiais, para especificar quais atributos e métodos são públicos ou privados. Os membros da classe pública podem ser acessados de qualquer lugar do programa. Membros de classe privada só são acessíveis por métodos na classe que os contém. Outras linguagens, como Python, usam convenções de codificação para indicar como um membro de classe deve ser tratado.
Em C#, qualquer membro de classe que deva ser mantido privado ou oculto de outras partes do programa deve começar
com a palavra-chave private. Qualquer membro de classe que deva estar disponível para uso em outras
partes do programa deve começar com a palavra-chave public. Eles são chamados modificadores de
acesso.
public class Conta
{
private List<int> _transacoes = new List<int>();
public void Depositar(int valor)
{
_transacoes.Add(valor);
}
}
Às vezes é difícil decidir quais membros da classe devem ser públicos ou privados. Uma boa regra prática é restringir o acesso aos membros da classe o máximo possível.
Escondendo informações de quem?
Tenha em mente que quando falamos em tornar dados privados ou ocultá-los, não estamos falando em criptografar dados confidenciais, como números de cartão de crédito, para ocultá-los do usuário ou de hackers. Em vez disso, estamos limitando as partes do nosso programa que podem acessar diretamente uma variável. Então estamos escondendo isso de nós mesmos em outras partes do programa.
A ideia é limitar o acesso apenas às partes do código das quais eles realmente precisam.
Atributos
Como regra geral, atributos (ou variáveis membro) devem ser privados. Outras classes não devem saber os detalhes de como a classe armazena suas informações. Em vez disso, as outras partes do programa devem pedir à classe para executar tarefas relacionadas a essas informações por meio de métodos públicos.
O exemplo a seguir mostra como declarar variáveis membro privadas:
public class Pessoa
{
private string _cargo;
private string _primeiroNome;
private string _sobrenome;
...
}
Métodos
Muitos métodos (ou funções membro) de uma classe são públicos. Esta é a interface que outras partes do programa podem usar para executar as tarefas necessárias.
Existem métodos privados? Sim! Muitas vezes, um método público terá etapas ou cálculos internos a serem executados para ajudá-lo a fazer seu trabalho. Essas "funções auxiliares" geralmente são privadas, porque o único código que precisa acessá-las está na classe.
Conforme dito acima, a regra geral a seguir é: restringir o acesso o máximo possível. Deixe um atributo ou método público somente se houver uma necessidade específica.
Exemplo
O exemplo a seguir mostra como declarar funções membro públicas e privadas. Ele dá exemplos de como obter dois tipos de assinaturas de e-mail: uma formal ("Atenciosamente") e outra informal ("Obrigado"). O formal faz uso do nome completo da pessoa.
Para obter o nome completo de uma classe que armazena o primeiro e o último nome separadamente, a classe pode usar uma função auxiliar privada para preparar o nome completo. Essa função poderia ser tornada privada inicialmente, porque outros só precisam acessar a assinatura de e-mail. (Se mais tarde for determinado que outras pessoas precisam ter acesso ao nome completo, você também pode considerar deixá-lo público. Mas, em todos os casos, você mantém as variáveis privadas.)
public class Pessoa
{
private string _titulo;
private string _primeiroNome;
private string _sobrenome;
public string ObterAssinaturaInformal()
{
return "Obrigado, " + _primeiroNome;
}
public string ObterAssinaturaFormal()
{
return "Atenciosamente, " + ObterNomeCompleto();
}
private string ObterNomeCompleto()
{
return _titulo + " " + _primeiroNome + " " + _sobrenome;
}
...
}
Getters e Setters
Às vezes, você pode achar que outras partes do programa precisam de acesso a uma variável membro. Se esse for o caso, em vez de tornar a variável pública, você deve criar métodos para obter e definir o valor. Isso garante que a classe ainda controle o acesso às variáveis e que as próprias variáveis fiquem escondidas.
Essas funções são chamadas de "getters" e "setters", ou métodos de acesso e modificação, e são muito comuns. Em inglês, o verbo "get" pode significar "obter", "pegar", ou "acessar", e "set" pode significar "definir", "criar", ou "estabelecer". Então, você pode pensar em um getter como um método que "obtém" o valor de uma variável, e um setter como um método que "define" o valor de uma variável. A partir daqui, usaremos os termos getter e setter conforme a convenção em programação.
O exemplo a seguir mostra como criar um getter e setter para a variável _primeiroNome:
public class Pessoa
{
private string _titulo;
private string _primeiroNome;
private string _sobrenome;
public string ObterPrimeiroNome()
{
return _primeiroNome;
}
public void DefinirPrimeiroNome(string primeiroNome)
{
_primeiroNome = primeiroNome;
}
...
}
Isso seria então chamado de outra parte do programa da seguinte maneira:
Pessoa p = new Pessoa();
p.DefinirPrimeiroNome("Pedro");
Console.WriteLine(p.ObterPrimeiroNome());
Observe que a outra parte do programa agora tem a capacidade de definir o primeiro nome e também pode acessá-lo quando necessário, mas faz isso chamando esses métodos, em vez de interagir diretamente com as variáveis.
Sobre Getters e Setters
Getters e setters são tão comuns que muitos editores de código conhecidos os geram automaticamente para você. Mas eles também têm sido objeto de debate desde pelo menos 2003, quando Allen Holub publicou um artigo chamado Why Getter and Setter Methods Are Evil (Por que os métodos Getter e Setter são malignos) (conteúdo em inglês).
Embora o encorajemos a desenvolver sua própria opinião, achamos que o conselho do Sr. Holub é sensato. Ao programar com classes, concentre-se no que a classe deve fazer em vez de como ela fará isso, e muitos dos getters e setters no seu código desaparecerão naturalmente. Você simplesmente não terá que se preocupar com isso.
Propriedades de C#
Porque a noção de getters e setters é tão poderosa e tão comum, alguns criadores de linguagens de programação,
incluindo C#, criaram um
construto especial de linguagem para eles chamado propriedades. Essas propriedades permitem que
você
crie getters e setters que se comportam como métodos, mas podem ser usados mais como uma variável.
Você pode ver a sintaxe para essas propriedades em código na internet de uma maneira como
public int Idade { get; set; }.
Neste curso, decidimos deliberadamente evitar ensinar e usar essas propriedades por duas razões. Primeiro, elas não estão disponíveis em todas as linguagens. E segundo, elas podem causar confusão à medida que você está apenas começando e dar a você o modelo mental errado, por causa da maneira como elas borram a linha entre métodos e variáveis. Elas quase começam a parecer mágica e dão a impressão de que esta é uma boa maneira de tornar públicas variáveis de membro, e então você perde o próprio princípio da encapsulação.
Por todas essas razões, à medida que você começa a trabalhar com getters e setters, é recomendável usar os métodos getter e setter reais. Então, à medida que você se sentir mais confortável com eles, será uma transição fácil para você começar a usar propriedades.
Construtores
Na aula anterior você foi apresentado aos Construtores. Construtores são métodos especiais que são chamados
automaticamente quando um objeto é criado. Sua finalidade é ajudar a configurar o estado inicial de um objeto. O
nome de um construtor deve corresponder ao nome da classe, e o tipo de retorno deve ser deixado vazio (nem mesmo
void). O construtor mais simples é um construtor sem argumentos, que permite que um objeto seja
criado sem especificar nenhuma informação extra.
Pessoa p = new Pessoa(); // Isso chama um construtor sem argumentos e não precisa de nenhuma informação
Você pode usar este construtor para ajudar a definir valores padrão. Por exemplo, o código a seguir especifica um
construtor sem argumentos para definir um nome padrão para todos os novos objetos Pessoa que são
criados.
public class Pessoa
{
private string _titulo;
private string _primeiroNome;
private string _sobrenome;
public Pessoa()
{
_titulo = "";
_primeiroNome = "Anonimo";
_sobrenome = "Desconhecido";
}
...
}
Além de um construtor sem argumentos, talvez você queira ter outro para permitir que o usuário passe os valores de algumas das variáveis membro. Por exemplo, o código a seguir mostra uma classe com três construtores, um que não requer nenhuma informação, outro que aceita valores para o primeiro nome e sobrenome (mas não o título) e outro que aceita valores para todas as três variáveis membro.
public class Pessoa
{
private string _titulo;
private string _primeiroNome;
private string _sobrenome;
public Pessoa()
{
_titulo = "";
_primeiroNome = "Anonimo";
_sobrenome = "Desconhecido";
}
public Pessoa (string primeiroNome, string sobrenome)
{
_titulo = "";
_primeiroNome = primeiroNome;
_sobrenome = sobrenome;
}
public Pessoa (string titulo, string primeiroNome, string sobrenome)
{
_titulo = titulo;
_primeiroNome = primeiroNome;
_sobrenome = sobrenome;
}
...
}
Esses três construtores permitem que você crie um novo objeto de pessoa de qualquer uma dessas três maneiras:
Pessoa p1 = new Pessoa(); // Não passe nenhuma informação para obter os valores padrão
Pessoa p2 = new Pessoa("Joana", "Dutra"); // passe o primeiro nome e sobrenome
Pessoa p3 = new Pessoa("Sra.", "Joana", "Dutra"); // passe todas as três variáveis
Se você não especificar nenhum construtor, o C# fornecerá um construtor vazio e sem argumentos para você. Entretanto, se você especificar outro construtor que receba valores, não terá um construtor padrão, a menos que o crie explicitamente. Isso é realmente bom se você quiser forçar as pessoas a sempre passarem valores ao criar um novo objeto.
Você pode indicar construtores em seu diagrama de classes da mesma forma que métodos, mas sem um tipo de retorno. Por exemplo:
Resumidamente
Encapsulamento é o segundo princípio da programação com classes. Um componente essencial do encapsulamento é esconder ou controlar o acesso às informações. O uso cuidadoso de modificadores de acesso ajudará a proteger você e seus colegas de trabalho contra a quebra de seus programas.
Mas lembre-se também de que o encapsulamento é mais do que apenas tornar variáveis membro privadas. Com base no princípio da Abstração, o encapsulamento visa garantir que suas classes definam os comportamentos apropriados e, então, cuidar internamente dos detalhes necessários para executar esses comportamentos. Classes bem projetadas fazem uso cuidadoso de construtores e fornecem métodos significativos para qualquer coisa que precise ser feita, em vez de simplesmente expor os dados internos com getters e setters.
Mas os benefícios não param por aí. Pratique o encapsulamento diligentemente e suas abstrações se tornarão mais refinadas, seus objetos mais intencionais e suas classes mais compreensíveis. Continue trabalhando nisso e, com o tempo, seus programas serão muito mais flexíveis e fáceis de alterar.
Instruções da Atividade
Pratique o princípio do encapsulamento criando classes para armazenar uma fração, como 2/3. Como você se lembra das suas aulas de matemática, uma fração tem um numerador (número superior) e um denominador (número inferior). A fração pode ser expressa como dois números inteiros com uma barra entre eles, como 3/4, ou como um decimal, como 0.75.
Projetar as Classes
Você precisa criar uma classe para uma fração que tenha:
- Atributos para o numerador e o denominador
- Construtores
- Getters e setters para o numerador e o denominador
- Métodos para retornar representações das visualizações fracionária e decimal.
Um diagrama de classe para esta classe ficaria assim:
Inicie o Projeto
- Abra o projeto da classe no VS Code.
- Navegue até o projeto
Fracoesna pastasemana03. Encontre o arquivoProgram.cs, que será seu ponto de entrada para o programa. - Verifique se você consegue executar o projeto.
Crie a classe Fracao (Fração)
- Crie uma classe para armazenar frações.
- A classe deve estar em seu próprio arquivo.
- A classe deve ter os atributos dois atributos: numerador e denominador.
- Certifique-se de que os atributos sejam privados.
Crie os construtores
- Crie os seguintes construtores:
- Construtor que não possui parâmetros que inicializa o número como 1/1.
- Construtor que tem um parâmetro para o numerador e que inicializa o denominador como 1. Assim, se você passar o número 5, a fração será inicializada como 5/1.
- Construtor que possui dois parâmetros, um para o numerador e outro para o denominador.
- No seu arquivo
Program.cs, verifique se você consegue criar frações usando todos esses três construtores. Por exemplo, crie uma instância para 1/1 (usando o primeiro construtor), para 6/1 (usando o segundo construtor), para 6/7 (usando o terceiro construtor).
Crie os Getters e Setters
- Crie getters e setters para o numerador e para o denominador.
- No seu arquivo
Program.cs, verifique se você consegue chamar todos esses métodos e obter os valores corretos, usando setters para alterar os valores e, em seguida, getters para recuperar esses novos valores e exibi-los no console.
Crie métodos para retornar as representações
- Crie um método chamado
ObterFracaoEmTextoque retorna a fração no formato3/4. - Crie um método chamado
ObterFracaoEmDecimalque retorna umdoubleque é o resultado da divisão do numerador pelo denominador, como0,75. - Verifique se você pode chamar cada construtor e se pode recuperar e exibir as diferentes representações para algumas frações diferentes. Por exemplo, você poderia tentar:
- 1
- 5
- 3/4
- 1/3
Exemplo de Resultado
1/1
1
5/1
5
3/4
0.75
1/3
0.3333333333333333
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.
- Confirme (commit) e envie (push) seu código para seu repositório GitHub.
- Verifique se você consegue ver seu código atualizado no GitHub.
- Responda ao questionário no Canvas para relatar seu trabalho.