Запечатанные классы
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 22 из 70.
Иногда вам нужно прервать цепочку наследования. Ключевое слово sealed предотвращает наследование класса, фиксируя его реализацию так, чтобы никакой другой класс не мог его расширить.
public sealed class FinalDog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
// Это вызовет ошибку компиляции:
// public class SuperDog : FinalDog { }Зачем запечатывать класс? Безопасность и целостность архитектуры — распространенные причины. Когда вы тщательно спроектировали класс и не хотите, чтобы кто-то менял его поведение через наследование, запечатывание гарантирует, что ваша реализация останется неизменной.
Вы также можете запечатывать отдельные методы, а не целые классы. Это предотвращает дальнейшее переопределение, при этом позволяя наследовать сам класс:
public class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("Woof!");
}
}
public class Puppy : Dog
{
// Нельзя переопределить Speak() — он запечатан (sealed)
// Но можно добавлять новые методы
public void Whimper()
{
Console.WriteLine("Whimper...");
}
}Обратите внимание, что sealed в методе должен сочетаться с override. Вы можете запечатать только тот метод, который уже был виртуальным в родительском классе. Это дает вам точный контроль: разрешить некоторую настройку в иерархии наследования, а затем заблокировать ее в определенной точке.
Задание
ЛегкоДавайте создадим систему обработки платежей, которая демонстрирует, как использовать sealed для блокировки критически важных реализаций. Вы создадите иерархию, в которой определенные способы оплаты могут быть расширены, но наиболее безопасные из них запечатаны (sealed), чтобы предотвратить любые изменения.
Вы создадите четыре файла для организации вашего кода:
PaymentMethod.cs: Определите классPaymentMethodв пространстве именPayments. Этот базовый класс имеет свойствоName(string) и конструктор, который его устанавливает. Включитеvirtualметод с именемProcessPayment(decimal amount), который возвращает"Processing ${amount} via {Name}".CreditCardPayment.cs: Определите классCreditCardPaymentв пространстве именPayments, который наследуется отPaymentMethod. Добавьте свойствоCardNumber(string). Конструктор принимает имя и номер карты, используяbaseдля передачи имени. ПереопределитеProcessPaymentс ключевым словомsealed, чтобы никакие дальнейшие производные классы не могли изменить это поведение. Он должен возвращать"Charging ${amount} to card ending in {last 4 digits of CardNumber}".BankTransfer.cs: Определитеsealedкласс с именемBankTransferв пространстве именPayments, который наследуется отPaymentMethod. Весь этот класс запечатан — никто не может наследоваться от него. Добавьте свойствоBankName(string). Конструктор принимает имя и название банка. ПереопределитеProcessPayment, чтобы он возвращал"Transferring ${amount} from {BankName}".Program.cs: В вашем основном файле создайте экземпляры всех трех типов платежей, используя входные значения. Сохраните их как ссылкиPaymentMethodи вызовитеProcessPaymentдля каждого, чтобы продемонстрировать полиморфизм с запечатанными реализациями.
Вы получите три входных значения:
- Номер кредитной карты (16 цифр)
- Название банка
- Сумма платежа
Выведите результат обработки платежа каждым методом:
{base PaymentMethod result}
{CreditCardPayment result}
{BankTransfer result}Например, если входные данные — 1234567890123456, First National и 150.00, вывод должен быть следующим:
Processing $150.00 via Generic
Charging $150.00 to card ending in 3456
Transferring $150.00 from First NationalИспользуйте "Generic" в качестве имени для базового экземпляра PaymentMethod, "Credit Card" для платежа по кредитной карте и "Bank Transfer" для банковского перевода.
Запечатанный метод в CreditCardPayment гарантирует, что если кто-то создаст подкласс, он не сможет изменить способ обработки платежей по кредитным картам. Запечатанный класс BankTransfer вообще предотвращает любое наследование — оба этих паттерна являются важными шаблонами безопасности в платежных системах!
Шпаргалка
Ключевое слово sealed предотвращает наследование класса или дальнейшее переопределение метода в цепочке наследования.
Запечатывание класса:
public sealed class FinalDog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
// Это вызовет ошибку компиляции:
// public class SuperDog : FinalDog { }Запечатанные классы используются для обеспечения безопасности и целостности архитектуры, гарантируя, что реализация не может быть изменена через наследование.
Запечатывание метода:
public class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("Woof!");
}
}
public class Puppy : Dog
{
// Невозможно переопределить Speak() — он запечатан
// Но можно добавлять новые методы
public void Whimper()
{
Console.WriteLine("Whimper...");
}
}Ключевое слово sealed для метода должно использоваться вместе с override. Вы можете запечатывать только те методы, которые уже были помечены как virtual в родительском классе. Это позволяет настраивать поведение до определенного момента в иерархии, а затем фиксирует его.
Попробуйте сами
using System;
using Payments;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
string cardNumber = Console.ReadLine();
string bankName = Console.ReadLine();
decimal amount = Convert.ToDecimal(Console.ReadLine());
// TODO: Создайте базовый экземпляр PaymentMethod с именем "Generic"
// Сохраните его как ссылку на PaymentMethod
// TODO: Создайте экземпляр CreditCardPayment с именем "Credit Card" и номером карты
// Сохраните его как ссылку на PaymentMethod
// TODO: Создайте экземпляр BankTransfer с именем "Bank Transfer" и названием банка
// Сохраните его как ссылку на PaymentMethod
// TODO: Вызовите ProcessPayment для каждого и выведите результаты
// Каждый результат должен быть на отдельной строке
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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'Необязательные параметрыИменованные аргументыПерегрузка методов