Паттерны неизменяемости
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 34 из 70.
Неизменяемость доводит инкапсуляцию до логического предела — как только объект создан, он больше никогда не может быть изменен. Это устраняет целые категории багов, так как вам больше не нужно беспокоиться о том, что кто-то или что-то неожиданно изменит ваши данные.
Самый простой способ создать неизменяемый класс — использовать поля readonly с конструктором, который устанавливает все значения, и не предоставлять сеттеры:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}После того как объект Point создан, его координаты не могут быть изменены. Но что, если вам нужна измененная версия? Вместо изменения существующего объекта вы возвращаете новый:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Исходная точка остается неизменной, в то время как вы получаете новую точку с обновленными значениями. Этот паттерн используется во всем .NET — строки, например, являются неизменяемыми. Когда вы вызываете ToUpper(), вы получаете новую строку, а не изменяете исходную.
Неизменяемые объекты потокобезопасны по своей природе, так как никакой код не может изменить их после создания. О них также легче рассуждать, поскольку вы знаете, что их состояние никогда не изменится неожиданно.
Задание
ЛегкоДавайте создадим неизменяемую (immutable) денежную систему, которая демонстрирует, как объекты могут быть спроектированы так, чтобы никогда не меняться после создания. Вы создадите класс Money, в котором каждая операция возвращает новый объект вместо изменения оригинала — этот паттерн делает ваш код более безопасным и простым для понимания.
Вы организуете свой код в двух файлах:
Money.cs: Создайте неизменяемый классMoneyв пространстве именFinance. Ваши деньги должны иметь:- Два поля
readonly:Amount(decimal) иCurrency(string) - Конструктор, который устанавливает оба значения — после создания объект Money никогда не может измениться
- Метод
Add(decimal value), который возвращает новый объектMoneyс увеличенной суммой (та же валюта) - Метод
Subtract(decimal value), который возвращает новый объектMoneyс уменьшенной суммой (та же валюта) - Метод
Convert(string newCurrency, decimal rate), который возвращает новый объектMoneyс суммой, умноженной на курс (rate), и новой валютой - Метод
GetDisplay(), который возвращает строку в формате"{Amount} {Currency}"
Money!- Два поля
Program.cs: В основном файле продемонстрируйте неизменяемость в действии. Создайте начальный объектMoney, затем выполните над ним операции, сохраняя ссылки как на оригинал, так и на результаты. Это докажет, что оригинал никогда не меняется даже после нескольких операций.
Вы получите четыре входных значения:
- Начальная сумма (decimal)
- Начальная валюта (string)
- Сумма для добавления (decimal)
- Курс конвертации в EUR (decimal)
Выведите результат в следующем формате:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}Например, если входные данные 100.00, USD, 50.00 и 0.85, вывод должен быть таким:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURОбратите внимание, что исходный объект Money по-прежнему показывает 100.00 USD даже после вызова Add() — это и есть неизменяемость! Метод Add() создал совершенно новый объект с 150.00 USD, оставив оригинал нетронутым. Конвертация выполняется над результатом операции сложения.
Шпаргалка
Неизменяемость (immutability) означает, что после создания объекта его состояние не может быть изменено. Это исключает ошибки, связанные с неожиданной модификацией данных.
Создайте неизменяемый класс, используя поля readonly с конструктором и без сеттеров:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Чтобы «изменить» неизменяемый объект, верните новый экземпляр вместо изменения существующего:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Преимущества неизменяемых объектов:
- Потокобезопасность по определению, так как никакой код не может изменить их после создания
- О них легче рассуждать, так как их состояние никогда не меняется неожиданно
- Широко используются в .NET (например, строки являются неизменяемыми)
Попробуйте сами
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
string initialCurrency = Console.ReadLine();
decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
decimal conversionRate = Convert.ToDecimal(Console.ReadLine());
// TODO: Создайте начальный объект Money с начальной суммой и валютой
// TODO: Выведите исходные деньги, используя GetDisplay()
// TODO: Вызовите Add() для оригинала и сохраните результат в новую переменную
// TODO: Выведите результат после сложения, используя GetDisplay()
// TODO: Снова выведите оригинал, чтобы доказать, что он не изменился
// TODO: Конвертируйте полученный результат в EUR, используя курс конвертации
// TODO: Выведите конвертированные деньги, используя GetDisplay()
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов