← Назад к вопросам
Что такое Абстрактный класс?
1.0 Junior🔥 241 комментариев
#ООП и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Абстрактный класс в C#
Абстрактный класс (Abstract Class) - это класс, который не может быть напрямую инстанцирован и служит базисом для других классов. Он определяет структуру и поведение, которые должны реализовать наследующие его классы.
Определение абстрактного класса
public abstract class Animal
{
// Конкретное свойство
public string Name { get; set; }
// Конкретный метод - может быть переопределён
public virtual void Eat()
{
Console.WriteLine($"{Name} is eating");
}
// Абстрактный метод - ОБЯЗАН быть реализован в наследнике
public abstract void MakeSound();
// Абстрактный метод - ОБЯЗАН быть реализован
public abstract void Move();
}
// ОШИБКА - не можно инстанцировать абстрактный класс
var animal = new Animal(); // Compilation error!
// Наследуемый класс ДОЛЖЕН реализовать все абстрактные методы
public class Dog : Animal
{
// ОБЯЗАТЕЛЬНО - реализация абстрактного метода
public override void MakeSound()
{
Console.WriteLine($"{Name} says: Woof!");
}
// ОБЯЗАТЕЛЬНО - реализация абстрактного метода
public override void Move()
{
Console.WriteLine($"{Name} runs on four legs");
}
}
public class Bird : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} says: Tweet!");
}
public override void Move()
{
Console.WriteLine($"{Name} flies in the sky");
}
}
// Теперь можно инстанцировать конкретные классы
var dog = new Dog { Name = "Rex" };
dog.MakeSound(); // Rex says: Woof!
dog.Eat(); // Rex is eating
dog.Move(); // Rex runs on four legs
var bird = new Bird { Name = "Tweety" };
bird.MakeSound(); // Tweety says: Tweet!
bird.Move(); // Tweety flies in the sky
Различия между Interface и Abstract Class
// ИНТЕРФЕЙС - контракт ТОЛЬКО с методами
public interface IAnimal
{
void MakeSound();
void Move();
}
// АБСТРАКТНЫЙ КЛАСС - может иметь реализацию и состояние
public abstract class Animal
{
// Может иметь поля и конкретное поведение
public string Name { get; set; }
public int Age { get; set; }
// Конкретные методы с реализацией
public virtual void Eat()
{
Console.WriteLine("Eating...");
}
// Абстрактные методы - должны быть реализованы
public abstract void MakeSound();
public abstract void Move();
}
Таблица различий:
| Аспект | Interface | Abstract Class |
|---|---|---|
| Инстанцирование | Нельзя | Нельзя |
| Состояние (поля) | Нет | Да |
| Конкретные методы | Нет (C# 8.0+) | Да |
| Наследование | Множественное | Одиночное |
| Модификаторы доступа | public/internal | any |
| Конструкторы | Нет | Да |
| Деструкторы | Нет | Да |
Практические примеры
1. Платёжные системы
public abstract class PaymentProcessor
{
// Общее состояние
public string MerchantId { get; set; }
public decimal Commission { get; set; }
// Общая реализация
public decimal CalculateTotal(decimal amount)
{
return amount + (amount * Commission / 100);
}
// Абстрактные методы - каждая система реализует по-своему
public abstract void Authenticate();
public abstract Task<bool> ProcessPaymentAsync(decimal amount);
public abstract void Refund(string transactionId);
}
public class StripeProcessor : PaymentProcessor
{
public override void Authenticate()
{
// Аутентификация в Stripe
}
public override async Task<bool> ProcessPaymentAsync(decimal amount)
{
var total = CalculateTotal(amount);
// Логика Stripe
return true;
}
public override void Refund(string transactionId)
{
// Возврат Stripe
}
}
public class PayPalProcessor : PaymentProcessor
{
public override void Authenticate()
{
// Аутентификация в PayPal
}
public override async Task<bool> ProcessPaymentAsync(decimal amount)
{
var total = CalculateTotal(amount);
// Логика PayPal
return true;
}
public override void Refund(string transactionId)
{
// Возврат PayPal
}
}
2. Логирование
public abstract class Logger
{
// Общее состояние
protected LogLevel MinimumLevel { get; set; }
// Конкретный метод - использует абстрактный
public void Log(string message, LogLevel level)
{
if (level >= MinimumLevel)
{
WriteLog(message, level);
}
}
// Абстрактный метод
protected abstract void WriteLog(string message, LogLevel level);
}
public class ConsoleLogger : Logger
{
protected override void WriteLog(string message, LogLevel level)
{
Console.ForegroundColor = level switch
{
LogLevel.Error => ConsoleColor.Red,
LogLevel.Warning => ConsoleColor.Yellow,
_ => ConsoleColor.White
};
Console.WriteLine($"[{level}] {message}");
}
}
public class FileLogger : Logger
{
private readonly string _filePath;
public FileLogger(string filePath)
{
_filePath = filePath;
}
protected override void WriteLog(string message, LogLevel level)
{
var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
File.AppendAllText(_filePath, logMessage + Environment.NewLine);
}
}
public class CloudLogger : Logger
{
private readonly ICloudService _cloudService;
public CloudLogger(ICloudService cloudService)
{
_cloudService = cloudService;
}
protected override void WriteLog(string message, LogLevel level)
{
_cloudService.SendLogAsync(message, level);
}
}
public enum LogLevel
{
Debug = 1,
Info = 2,
Warning = 3,
Error = 4
}
3. Репозитории данных
public abstract class Repository<T> where T : class
{
protected DbContext DbContext { get; set; }
// Конкретные методы
public virtual async Task<T> GetByIdAsync(int id)
{
return await DbContext.Set<T>().FindAsync(id);
}
public virtual async Task<IEnumerable<T>> GetAllAsync()
{
return await DbContext.Set<T>().ToListAsync();
}
// Абстрактные методы для специфичной логики
public abstract Task<T> GetWithRelatedAsync(int id);
public abstract IQueryable<T> ApplyFilters(IQueryable<T> query, FilterParams filters);
}
public class UserRepository : Repository<User>
{
public override async Task<User> GetWithRelatedAsync(int id)
{
return await DbContext.Users
.Include(u => u.Orders)
.Include(u => u.Profile)
.FirstOrDefaultAsync(u => u.Id == id);
}
public override IQueryable<User> ApplyFilters(IQueryable<User> query, FilterParams filters)
{
if (!string.IsNullOrEmpty(filters.Name))
query = query.Where(u => u.Name.Contains(filters.Name));
if (filters.MinAge.HasValue)
query = query.Where(u => u.Age >= filters.MinAge);
return query;
}
}
Ключевые особенности
1. Абстрактные методы ОБЯЗАТЕЛЬНЫ для реализации
public abstract class Shape
{
public abstract double GetArea(); // ОБЯЗАТЕЛЕН
}
public class Circle : Shape
{
private double _radius;
public override double GetArea() // ОБЯЗАТЕЛЬНО переопределить
{
return Math.PI * _radius * _radius;
}
}
// Ошибка - не реализована GetArea()
public class Square : Shape // Compilation error!
{
// ...
}
2. Конкретные методы можно переопределить
public abstract class Vehicle
{
public virtual void Start()
{
Console.WriteLine("Starting engine...");
}
public abstract void Drive();
}
public class Car : Vehicle
{
// Переопределяем конкретный метод
public override void Start()
{
Console.WriteLine("Car engine started");
base.Start();
}
// Реализуем абстрактный метод
public override void Drive()
{
Console.WriteLine("Car is driving");
}
}
3. Конструкторы в абстрактных классах
public abstract class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
// Конструктор
protected Employee(string name, decimal salary)
{
Name = name;
Salary = salary;
}
public abstract decimal CalculateBonus();
}
public class Manager : Employee
{
public Manager(string name, decimal salary) : base(name, salary)
{
}
public override decimal CalculateBonus()
{
return Salary * 0.2m; // 20% бонус для менеджеров
}
}
Когда использовать абстрактный класс
✅ Используй, если:
- Нужно общее состояние (поля)
- Нужна конкретная реализация некоторых методов
- Классы тесно связаны и используют общий код
- Нужны конструкторы или деструкторы
- Нужна защита (protected, private члены)
❌ Не используй, если:
- Только интерфейс (контракт методов)
- Классы не связаны
- Нужно множественное наследование
Заключение
Абстрактный класс - это мощный инструмент для:
- Определения общей структуры для группы классов
- Обеспечения кода многоуровневой иерархией
- Создания контрактов, которые ОБЯЗАНЫ реализовать потомки
- Совместного использования конкретного кода между классами
Он более мощный, чем интерфейс, но более ограничивающий (одиночное наследование). Выбирай правильный инструмент для задачи.