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 el encapsulamiento a su extremo lógico: una vez que se crea un objeto, 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 ningún setter:
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, ya que ningún código puede modificarlos después de su creación. También es más fácil razonar sobre ellos porque sabes que su estado nunca cambiará de forma inesperada.
Desafío
FácilVamos a construir 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 donde 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 claseMoneyinmutable en el espacio de nombresFinance. Tu dinero debe tener:- Dos campos
readonly:Amount(decimal) yCurrency(string) - Un constructor que establece ambos valores: una vez creado, un objeto Money nunca puede cambiar
- Un método
Add(decimal value)que devuelve un nuevo objetoMoneycon el monto incrementado (misma moneda) - Un método
Subtract(decimal value)que devuelve un nuevo objetoMoneycon el monto disminuido (misma moneda) - Un método
Convert(string newCurrency, decimal rate)que devuelve un nuevo objetoMoneycon el monto multiplicado por la tasa y la nueva moneda - Un método
GetDisplay()que devuelve una cadena en el formato"{Amount} {Currency}"
Moneycompletamente nuevas!- Dos campos
Program.cs: En tu archivo principal, demuestra la inmutabilidad en acción. Crea un objetoMoneyinicial, luego 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 múltiples operaciones.
Recibirás cuatro entradas:
- Monto inicial (decimal)
- Moneda inicial (string)
- Monto a sumar (decimal)
- Tasa de conversión a EUR (decimal)
Imprime la salida en 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 todavía muestra 100.00 USD incluso después de llamar a Add(); ¡eso es inmutabilidad! El método Add() creó un objeto completamente nuevo con 150.00 USD, dejando el original intacto. La conversión se realiza sobre el resultado de la operación de suma.
Hoja de referencia
La inmutabilidad significa que una vez que se crea un objeto, este nunca puede cambiar. Esto elimina errores relacionados con modificaciones de datos inesperadas.
Cree una clase inmutable utilizando campos readonly con un constructor y sin setters:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Para modificar un objeto inmutable, devuelva una nueva instancia en lugar de cambiar la existente:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Beneficios de los objetos inmutables:
- Seguros para hilos (thread-safe) por diseño, ya que ningún código puede modificarlos después de su creación
- Más fáciles de razonar porque su estado nunca cambia inesperadamente
- Utilizados en todo .NET (por ejemplo, las cadenas son inmutables)
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: Crear un objeto Money inicial con el monto y la moneda iniciales
// TODO: Imprimir el dinero original usando GetDisplay()
// TODO: Llamar a Add() en el original y almacenar el resultado en una nueva variable
// TODO: Imprimir el resultado después de sumar usando GetDisplay()
// TODO: Imprimir el original de nuevo para demostrar que no ha cambiado
// TODO: Convertir el resultado sumado a EUR usando la tasa de conversión
// TODO: Imprimir 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 estudiantes