Menu
Coddy logo textTech

Запечатанные классы

Часть раздела Объектно-ориентированное программирование путешествия по 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. Вы можете запечатать только тот метод, который уже был виртуальным в родительском классе. Это дает вам точный контроль: разрешить некоторую настройку в иерархии наследования, а затем заблокировать ее в определенной точке.

challenge icon

Задание

Легко

Давайте создадим систему обработки платежей, которая демонстрирует, как использовать 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 для каждого и выведите результаты
        // Каждый результат должен быть на отдельной строке
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование