Padrões de Imutabilidade
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 34 de 70.
A imutabilidade leva o encapsulamento ao seu extremo lógico - uma vez que um objeto é criado, ele nunca pode mudar. Isso elimina categorias inteiras de bugs porque você nunca precisa se preocupar com algo modificando seus dados inesperadamente.
A maneira mais simples de criar uma classe imutável é usar campos readonly com um construtor que define todos os valores e não fornecer nenhum setter:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Uma vez que um Point é criado, suas coordenadas não podem mudar. Mas e se você precisar de uma versão modificada? Em vez de alterar o objeto existente, você retorna um novo:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}O ponto original permanece inalterado enquanto você obtém um novo ponto com valores atualizados. Este padrão é usado em todo o .NET - strings, por exemplo, são imutáveis. Quando você chama ToUpper(), você obtém uma nova string em vez de modificar a original.
Objetos imutáveis são inerentemente seguros para threads, já que nenhum código pode modificá-los após a criação. Eles também são mais fáceis de raciocinar sobre, pois você sabe que seu estado nunca mudará inesperadamente.
Desafio
FácilVamos construir um sistema monetário imutável que demonstra como objetos podem ser projetados para nunca mudarem após a criação. Você criará uma classe Money onde cada operação retorna um novo objeto em vez de modificar o original - um padrão que torna seu código mais seguro e fácil de raciocinar.
Você organizará seu código em dois arquivos:
Money.cs: Crie uma classeMoneyimutável no namespaceFinance. Seu dinheiro deve ter:- Dois campos
readonly:Amount(decimal) eCurrency(string) - Um construtor que define ambos os valores - uma vez criado, um objeto Money nunca pode mudar
- Um método
Add(decimal value)que retorna um novo objetoMoneycom o valor aumentado (mesma moeda) - Um método
Subtract(decimal value)que retorna um novo objetoMoneycom o valor diminuído (mesma moeda) - Um método
Convert(string newCurrency, decimal rate)que retorna um novo objetoMoneycom o valor multiplicado pela taxa e a nova moeda - Um método
GetDisplay()que retorna uma string no formato"{Amount} {Currency}"
Moneynovinhas em folha!- Dois campos
Program.cs: No seu arquivo principal, demonstre a imutabilidade em ação. Crie um objetoMoneyinicial e, em seguida, realize operações nele mantendo referências tanto para o original quanto para os resultados. Isso provará que o original nunca muda, mesmo após múltiplas operações.
Você receberá quatro entradas:
- Valor inicial (decimal)
- Moeda inicial (string)
- Valor para adicionar (decimal)
- Taxa de conversão para EUR (decimal)
Imprima a saída neste formato:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}Por exemplo, se as entradas forem 100.00, USD, 50.00, e 0.85, a saída deve ser:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURObserve como o objeto Money original ainda mostra 100.00 USD mesmo após chamar Add() - isso é imutabilidade! O método Add() criou um objeto completamente novo com 150.00 USD, deixando o original intocado. A conversão é realizada no resultado da operação de adição.
Folha de consulta
Imutabilidade significa que, uma vez que um objeto é criado, ele nunca pode mudar. Isso elimina bugs relacionados a modificações inesperadas de dados.
Crie uma classe imutável usando campos readonly com um construtor e sem setters:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Para modificar um objeto imutável, retorne uma nova instância em vez de alterar a existente:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Benefícios de objetos imutáveis:
- Seguros para threads (thread-safe) por design, já que nenhum código pode modificá-los após a criação
- Mais fáceis de raciocinar sobre, porque seu estado nunca muda inesperadamente
- Usados em todo o .NET (por exemplo, strings são imutáveis)
Experimente você mesmo
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Ler as entradas
decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
string initialCurrency = Console.ReadLine();
decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
decimal conversionRate = Convert.ToDecimal(Console.ReadLine());
// TODO: Criar um objeto Money inicial com o valor e a moeda iniciais
// TODO: Imprimir o dinheiro original usando GetDisplay()
// TODO: Chamar Add() no original e armazenar o resultado em uma nova variável
// TODO: Imprimir o resultado após a adição usando GetDisplay()
// TODO: Imprimir o original novamente para provar que ele não foi alterado
// TODO: Converter o resultado somado para EUR usando a taxa de conversão
// TODO: Imprimir o dinheiro convertido usando GetDisplay()
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de Métodos