Patrones de inmutabilidad
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy. Lección 34 de 70.
La inmutabilidad lleva la encapsulación a su extremo lógico: una vez que se crea un objeto, este nunca puede cambiar. Esto elimina categorías enteras de errores porque nunca tienes que preocuparte de que algo modifique tus datos de forma inesperada.
La forma más sencilla de crear una clase inmutable es usar campos readonly con un constructor que establezca todos los valores y no proporcionar setters:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Una vez que se crea un Point, sus coordenadas no pueden cambiar. ¿Pero qué pasa si necesitas una versión modificada? En lugar de cambiar el objeto existente, devuelves uno nuevo:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}El punto original permanece sin cambios mientras obtienes un nuevo punto con valores actualizados. Este patrón se utiliza en todo .NET; las cadenas, por ejemplo, son inmutables. Cuando llamas a ToUpper(), obtienes una nueva cadena en lugar de modificar la original.
Los objetos inmutables son intrínsecamente seguros para subprocesos (thread-safe), ya que ningún código puede modificarlos después de su creación. También son más fáciles de razonar porque sabes que su estado nunca cambiará de forma inesperada.
Desafío
FácilConstruyamos un sistema de dinero inmutable que demuestre cómo se pueden diseñar objetos para que nunca cambien después de su creación. Crearás una clase Money en la que cada operación devuelve un objeto nuevo en lugar de modificar el original, un patrón que hace que tu código sea más seguro y fácil de razonar.
Organizarás tu código en dos archivos:
Money.cs: Crea una clase inmutableMoneyen el espacio de nombresFinance. Tu sistema monetario debe tener:- Dos campos
readonly:Amount(decimal) yCurrency(string) - Un constructor que establezca ambos valores; una vez creado, un objeto Money nunca puede cambiar
- Un método
Add(decimal value)que devuelva un objetoMoneynuevo con el importe aumentado (la misma moneda) - Un método
Subtract(decimal value)que devuelva un objetoMoneynuevo con el importe reducido (la misma moneda) - Un método
Convert(string newCurrency, decimal rate)que devuelva un objetoMoneynuevo con el importe multiplicado por la tasa y la nueva moneda - Un método
GetDisplay()que devuelva una cadena con el formato"{Amount} {Currency}"
Moneycompletamente nuevas!- Dos campos
Program.cs: En tu archivo principal, demuestra la inmutabilidad en acción. Crea un objetoMoneyinicial y después realiza operaciones sobre él mientras mantienes referencias tanto al original como a los resultados. Esto demostrará que el original nunca cambia, incluso después de varias operaciones.
Recibirás cuatro entradas:
- Importe inicial (decimal)
- Moneda inicial (string)
- Importe que se sumará (decimal)
- Tasa de conversión a EUR (decimal)
Imprime la salida con este formato:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}Por ejemplo, si las entradas son 100.00, USD, 50.00 y 0.85, la salida debería ser:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURObserva cómo el objeto Money original sigue mostrando 100.00 USD incluso después de llamar a Add(); ¡eso es la inmutabilidad! El método Add() creó un objeto completamente nuevo con 150.00 USD, dejando intacto el original. La conversión se realiza sobre el resultado de la operación de suma.
Pruébalo tú mismo
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Leer entradas
decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
string initialCurrency = Console.ReadLine();
decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
decimal conversionRate = Convert.ToDecimal(Console.ReadLine());
// TODO: Create un objeto Money inicial con el monto y la moneda iniciales
// TODO: Imprime el dinero original usando GetDisplay()
// TODO: Llama a Add() en el original y almacena el resultado en una nueva variable
// TODO: Imprime el resultado después de sumar usando GetDisplay()
// TODO: Imprime el original de nuevo para demostrar que no ha cambiado
// TODO: Convierte el resultado sumado a EUR usando el conversion rate
// TODO: Imprime el dinero convertido usando GetDisplay()
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantesPractica por tu cuenta: Compilador de C# online