Приведи пример применения принципов SOLID
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Практическое применение 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-механик, где изменения требований - обычное дело.