Комментарии (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) { }
}
Мой подход
- Начинаю с интерфейса если не уверен
- Переходу на abstract класс когда понимаю:
- Нужен shared code
- Нужно state
- Это реальная иерархия
- Использую оба вместе:
// Интерфейс определяет контракт
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 абстрактного класса.