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

Приведи пример применения принципов SOLID

1.8 Middle🔥 151 комментариев
#Паттерны проектирования

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Практическое применение SOLID в Unity: пример инвентаря и предметов

Разберем пример инвентаря в игре с применением всех пяти принципов SOLID, который я реализовывал в коммерческих проектах.

1. SRP (Single Responsibility Principle) - Единственная ответственность

// НЕПРАВИЛЬНО: Класс делает слишком много
public class Player : MonoBehaviour
{
    public List<Item> inventory;
    public int health;
    public float speed;
    
    public void AddItem(Item item) { /* логика инвентаря */ }
    public void TakeDamage(int damage) { /* логика здоровья */ }
    public void Move(Vector3 direction) { /* логика движения */ }
}

// ПРАВИЛЬНО: Разделяем ответственность
public class PlayerInventory : MonoBehaviour
{
    private List<Item> items = new List<Item>();
    private InventoryUI ui;
    
    public void AddItem(Item item)
    {
        items.Add(item);
        ui.UpdateInventory(items);
    }
    
    public bool HasItem(string itemId)
    {
        return items.Any(item => item.Id == itemId);
    }
}

Каждый класс теперь отвечает только за одну вещь: PlayerInventory управляет предметами, PlayerHealth - здоровьем, PlayerMovement - перемещением.

2. OCP (Open-Closed Principle) - Открыт для расширения, закрыт для изменения

// Базовый класс предмета
public abstract class Item : ScriptableObject
{
    public string Id;
    public string Name;
    public Sprite Icon;
    
    public abstract void Use(Player player);
}

// Конкретные реализации
public class HealthPotion : Item
{
    public int HealAmount = 50;
    
    public override void Use(Player player)
    {
        player.Health.Restore(HealAmount);
        Debug.Log($"Использовано зелье здоровья на {HealAmount} HP");
    }
}

public class ManaPotion : Item
{
    public int ManaRestore = 30;
    
    public override void Use(Player player)
    {
        player.Mana.Restore(ManaRestore);
        Debug.Log($"Восстановлено {ManaRestore} маны");
    }
}

Мы можем добавлять новые типы предметов (FireScroll, KeyItem, etc.), не изменяя существующий код базового класса Item.

3. LSP (Liskov Substitution Principle) - Подстановка Барбары Лисков

public interface IEquippable
{
    void Equip(Player player);
    void Unequip(Player player);
}

public class Weapon : Item, IEquippable
{
    public int Damage;
    
    public override void Use(Player player)
    {
        Equip(player);
    }
    
    public void Equip(Player player)
    {
        player.Equipment.EquipWeapon(this);
    }
    
    public void Unequip(Player player)
    {
        player.Equipment.UnequipWeapon();
    }
}

public class Armor : Item, IEquippable
{
    public int Defense;
    
    public override void Use(Player player)
    {
        Equip(player);
    }
    
    public void Equip(Player player)
    {
        player.Equipment.EquipArmor(this);
    }
    
    public void Unequip(Player player)
    {
        player.Equipment.UnequipArmor();
    }
}

// В коде можно работать через интерфейс
public void ProcessEquipment(List<IEquippable> equippables)
{
    foreach (var item in equippables)
    {
        item.Equip(player); // Работает для любого экипируемого предмета
    }
}

Любой класс, реализующий IEquippable, можно использовать взаимозаменяемо.

4. ISP (Interface Segregation Principle) - Разделение интерфейсов

// НЕПРАВИЛЬНО: Один большой интерфейс
public interface IItem
{
    void Use(Player player);
    void Sell(int price);
    void Repair();
    void Enchant();
}

// ПРАВИЛЬНО: Разделенные интерфейсы
public interface IUsable
{
    void Use(Player player);
}

public interface ISellable
{
    int BasePrice { get; }
    void Sell(Player player);
}

public interface IRepairable
{
    int Durability { get; }
    void Repair();
}

public interface IEnchantable
{
    void ApplyEnchantment(Enchantment enchantment);
}

// Классы реализуют только нужные интерфейсы
public class ConsumablePotion : Item, IUsable, ISellable
{
    public int BasePrice => 100;
    
    public override void Use(Player player) { /* логика использования */ }
    public void Sell(Player player) { /* логика продажи */ }
}

public class EpicWeapon : Weapon, IUsable, IRepairable, IEnchantable
{
    public int Durability => 1000;
    
    public void Repair() { /* логика починки */ }
    public void ApplyEnchantment(Enchantment enchantment) { /* логика зачарования */ }
}

Классы не обязаны реализовывать методы, которые им не нужны.

5. DIP (Dependency Inversion Principle) - Инверсия зависимостей

public interface IInventoryService
{
    void AddItem(Item item);
    void RemoveItem(string itemId);
    bool HasItem(string itemId);
}

public class PlayerInventory : MonoBehaviour, IInventoryService
{
    private List<Item> items = new List<Item>();
    
    public void AddItem(Item item) { items.Add(item); }
    public void RemoveItem(string itemId) { /* удаление */ }
    public bool HasItem(string itemId) { return items.Any(i => i.Id == itemId); }
}

public class QuestSystem : MonoBehaviour
{
    private IInventoryService inventoryService;
    
    // Внедрение зависимости через конструктор/инспектор
    [SerializeField] private PlayerInventory inventory;
    
    private void Start()
    {
        inventoryService = inventory;
    }
    
    public bool CheckQuestRequirement(string requiredItemId)
    {
        // Зависим от абстракции, а не от конкретной реализации
        return inventoryService.HasItem(requiredItemId);
    }
}

// В расширенном варианте - использование DI контейнера
public class TradeSystem : MonoBehaviour
{
    [Inject] private IInventoryService inventoryService;
    
    public void TradeItem(Item item, int price)
    {
        if (inventoryService.HasItem(item.Id))
        {
            inventoryService.RemoveItem(item.Id);
            // логика торговли
        }
    }
}

Результат применения SOLID:

  • Система инвентаря легко расширяется новыми типами предметов
  • Код становится тестируемым (можно создавать mock-инвентарь для тестов)
  • Разные программисты могут параллельно работать над разными компонентами
  • Минимальная связность между системами игры
  • Легкость рефакторинга и поддержки кода в долгосрочной перспективе

В реальных проектах Unity соблюдение этих принципов особенно важно при создании сложных систем типа диалогов, квестов, крафта или RPG-механик, где изменения требований - обычное дело.