← Назад к вопросам

Когда используешь абстрактный класс?

1.3 Junior🔥 131 комментариев
#C# и ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Когда используешь абстрактный класс

Это отличный вопрос о различии между абстрактными классами и интерфейсами в C#. В контексте Unity это важно для правильной архитектуры.

Абстрактный класс vs интерфейс

Долго искал чёткое правило, вот что я вывел:

Абстрактный класс используй, когда:

  • Есть shared implementation (общий код)
  • Есть state (данные)
  • Есть access modifiers (protected, private)
  • Есть иерархия классов

Интерфейс используй, когда:

  • Только контракт, без реализации
  • Множественная реализация
  • Unrelated классы должны реализовать один интерфейс

Практический пример в Unity

// ✅ Абстрактный класс — есть shared code
public abstract class Character : MonoBehaviour {
    protected int health = 100;
    protected Animator animator;
    
    protected virtual void Start() {
        animator = GetComponent<Animator>();
    }
    
    public virtual void TakeDamage(int amount) {
        health -= amount;
        animator.SetTrigger("Damage");
        
        if (health <= 0) {
            Die();
        }
    }
    
    protected abstract void Die(); // Must implement
}

public class Player : Character {
    protected override void Die() {
        // Специфичная для player логика смерти
        GameManager.Instance.GameOver();
        Destroy(gameObject);
    }
}

public class Enemy : Character {
    protected override void Die() {
        // Специфичная для enemy логика
        DropLoot();
        Destroy(gameObject);
    }
}

Здесь абстрактный класс идеален, потому что:

  • Shared code: TakeDamage(), health, animator
  • Protected state: health могут использовать наследники
  • Partial implementation: TakeDamage() реализован, Die() оставлен для наследников

Второй пример — интерфейсы

// ❌ Неправильно использовать абстрактный класс
public abstract class Damageable {
    public abstract void TakeDamage(int amount);
}

public class Player : Damageable {
    // Player наследует Damageable
}

public class Building : Damageable {
    // Building наследует Damageable
    // Но Building не персонаж, иерархия неправильная
}

// ✅ Правильно — интерфейс
public interface IDamageable {
    void TakeDamage(int amount);
}

public class Player : Character, IDamageable {
    public void TakeDamage(int amount) { }
}

public class Building : MonoBehaviour, IDamageable {
    public void TakeDamage(int amount) { }
}

public class Projectile : MonoBehaviour {
    public void HitTarget(IDamageable target) {
        target.TakeDamage(25); // Работает с Player и Building
    }
}

Когда я выбираю абстрактный класс

Случай 1: Иерархия типов

public abstract class Entity : MonoBehaviour {
    // Общая логика для всех сущностей
}

public class Character : Entity {
    // Специфичная для персонажей
}

public class Vehicle : Entity {
    // Специфичная для транспорта
}

Случай 2: Shared state и protected методы

public abstract class Weapon : MonoBehaviour {
    protected int ammo = 30;
    protected float fireRate = 0.1f;
    
    protected void DecrementAmmo() {
        ammo--;
    }
    
    public abstract void Fire();
}

Случай 3: Template Method Pattern

public abstract class GameState : MonoBehaviour {
    public void Enter() {
        ValidateState();
        OnEnter(); // Переопределяется наследниками
        StartCoroutine(nameof(StateLoop));
    }
    
    protected abstract void OnEnter();
    protected abstract IEnumerator StateLoop();
    
    private void ValidateState() {
        // Логика валидации для всех состояний
    }
}

Частая ошибка

// ❌ Неправильно — абстрактный класс для разных things
public abstract class Collidable {
    public abstract void OnCollision(Collider other);
}

public class Player : Collidable { }
public class Projectile : Collidable { }
public class Building : Collidable { }
// Но Player уже может наследовать Character!
// Множественное наследование от монолита неправильно

// ✅ Правильно
public interface ICollidable {
    void OnCollision(Collider other);
}

public class Player : Character, ICollidable {
    public void OnCollision(Collider other) { }
}

Мой подход

  1. Начинаю с интерфейса если не уверен
  2. Переходу на abstract класс когда понимаю:
    • Нужен shared code
    • Нужно state
    • Это реальная иерархия
  3. Использую оба вместе:
// Интерфейс определяет контракт
public interface IDamageable {
    void TakeDamage(int amount);
}

// Абстрактный класс с shared implementation
public abstract class Character : MonoBehaviour, IDamageable {
    protected int health = 100;
    
    public virtual void TakeDamage(int amount) {
        health -= amount;
        if (health <= 0) {
            Die();
        }
    }
    
    protected abstract void Die();
}

Это дает мне best of both worlds: контракт интерфейса и shared code абстрактного класса.

Когда используешь абстрактный класс? | PrepBro