Можно ли построить цепочку наследования, используя только абстрактные классы?

«Можно ли построить цепочку наследования, используя только абстрактные классы?» — вопрос из категории ООП, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, можно создать цепочку наследования, где несколько уровней представлены абстрактными классами. Это мощный инструмент для постепенной конкретизации абстракции.

Пример иерархии с абстрактными классами:

// Уровень 1: Самая общая абстракция
abstract class DataSource
{
    public abstract void Open(); // Абстрактный метод - обязан быть реализован
    public abstract void Close();
    public virtual string GetName() => "Unnamed Source"; // Виртуальный метод с реализацией по умолчанию
}

// Уровень 2: Уточнение абстракции, но всё ещё абстрактный
abstract class FileDataSource : DataSource
{
    public string FilePath { get; protected set; }
    // Не реализуем Open() и Close() - класс остаётся абстрактным
    public override string GetName() => $"File: {FilePath}"; // Переопределение виртуального метода
}

// Уровень 3: Конкретная реализация
class EncryptedFileDataSource : FileDataSource
{
    private byte[] _encryptionKey;
    public EncryptedFileDataSource(string path, byte[] key) {
        FilePath = path;
        _encryptionKey = key;
    }
    // Наконец реализуем все абстрактные методы
    public override void Open() {
        Console.WriteLine($"Opening and decrypting {FilePath}");
        // Логика открытия и расшифровки
    }
    public override void Close() {
        Console.WriteLine($"Closing {FilePath}");
        // Логика закрытия
    }
}

Ключевые правила и применение:

  1. Обязательность реализации: Любой неабстрактный класс-наследник в конце цепочки должен предоставить реализации для всех унаследованных абстрактных методов.
  2. Постепенная конкретизация: Каждый уровень может:
    • Добавлять новые абстрактные методы (abstract).
    • Добавлять новые конкретные методы и свойства.
    • Предоставлять реализации для виртуальных методов (virtual) базовых классов.
  3. Запрет на инстанцирование: Создать экземпляр абстрактного класса нельзя (new DataSource() — ошибка компиляции).
  4. Польза: Такая архитектура полезна для создания каркасов (frameworks), где вы определяете общий алгоритм (например, в методе Execute() базового абстрактного класса), который вызывает абстрактные шаги (Open, Process, Close), реализуемые в конкретных наследниках (Шаблонный метод).