6_Абстракция и пространства имён

Урок 6 : Абстракция и пространства имён

О чём этот урок

Абстракция позволяет работать с разными объектами через единый интерфейс, скрывая детали реализации. Пространства имён организуют код в крупных проектах.


1. Что такое абстракция в ООП

Абстракция - принцип ООП, при котором выделяются только существенные характеристики объекта, а детали реализации скрываются.

Проще говоря: вы знаете что делает объект, но не обязаны знать как именно.

// Пример из жизни: вы знаете, что у машины есть педаль газа.
// Вам не нужно знать, как работает двигатель внутри.

// В коде: вы знаете, что у любого оружия есть метод Attack().
// Реализация у меча и лука - разная, но вызываете одинаково.

В C# абстракция реализуется двумя главными инструментами:

  1. Абстрактные классы (abstract class)
  2. Интерфейсы (interface)

2. Абстрактный класс

Абстрактный класс - класс, который нельзя создать напрямую (new). Он задаёт общую структуру для потомков.

abstract class Shape
{
    public string Color { get; set; }  // конкретное поле - у всех фигур есть цвет

    // Абстрактный метод - нет реализации, потомок ОБЯЗАН переопределить
    public abstract double GetArea();

    // Конкретный метод - реализован, потомки могут использовать
    public void PrintInfo()
    {
        Console.WriteLine($"Фигура: {GetType().Name}, Цвет: {Color}, Площадь: {GetArea()}");
    }
}

class Circle : Shape
{
    private double _radius;

    public Circle(double radius) { _radius = radius; }

    public override double GetArea()  // обязательно переопределить
    {
        return Math.PI * _radius * _radius;
    }
}

class Rectangle : Shape
{
    private double _width, _height;

    public Rectangle(double w, double h) { _width = w; _height = h; }

    public override double GetArea() => _width * _height;
}

// Использование
Shape circle = new Circle(5);       // хранится как Shape
circle.Color = "Красный";
circle.PrintInfo();                  // "Фигура: Circle, Цвет: Красный, Площадь: 78.5"

// Shape s = new Shape(); // ОШИБКА: нельзя создать абстрактный класс

3. Модификаторы virtual и abstract

Модификатор На что влияет Обязательно переопределять? Есть реализация?
virtual Метод или свойство Нет (по желанию) Да
abstract Метод или свойство Да (обязательно) Нет
abstract class Enemy
{
    // abstract - нет реализации, ОБЯЗАН переопределить потомок
    public abstract void Attack();

    // virtual - есть реализация, потомок МОЖЕТ переопределить
    public virtual void Move()
    {
        Console.WriteLine("Идёт прямо");
    }
}

class Goblin : Enemy
{
    public override void Attack()         // обязательно
    {
        Console.WriteLine("Гоблин бьёт кинжалом");
    }

    public override void Move()           // по желанию
    {
        Console.WriteLine("Гоблин крадётся");
    }
}

class Dragon : Enemy
{
    public override void Attack()         // обязательно
    {
        Console.WriteLine("Дракон дышит огнём");
    }
    // Move() не переопределён - используется базовая реализация
}

4. Интерфейс

Интерфейс - это как должностная инструкция: она перечисляет, что обязан уметь сотрудник, но не говорит, как именно он это делает.

Набор методов и свойств, которые класс обязуется реализовать. Сам интерфейс не содержит реализации (в основном).

// Интерфейс объявляется с I (соглашение об именовании)
interface IDamageable
{
    int Health { get; }        // только подпись свойства
    void TakeDamage(int amount);  // только подпись метода
}

interface IHealable
{
    void Heal(int amount);
}

// Класс может реализовывать несколько интерфейсов
class Player : IDamageable, IHealable
{
    private int _health = 100;
    public int Health => _health;

    public void TakeDamage(int amount)
    {
        _health = Math.Max(0, _health - amount);
        Console.WriteLine($"Получен урон {amount}. HP: {_health}");
    }

    public void Heal(int amount)
    {
        _health = Math.Min(100, _health + amount);
        Console.WriteLine($"Восстановлено {amount}. HP: {_health}");
    }
}

// Работа через интерфейс
IDamageable target = new Player();
target.TakeDamage(30);
Класс реализует интерфейс - это обещание

Когда класс объявляет class Foo : IBar, он обещает: «У меня есть всё, что требует IBar». Если хотя бы один метод не реализован - ошибка компиляции.


5. Абстрактный класс vs Интерфейс

Характеристика Абстрактный класс Интерфейс
Наследование Только одно Несколько одновременно
Реализация методов Может иметь Нет (до C# 8)
Поля Да Нет
Конструктор Да Нет
Модификаторы доступа Да Нет (всё public)
Когда использовать Есть общая логика + общие данные Только контракт (что умеет делать)
Правило выбора

  • Абстрактный класс: объекты являются чем-то (Dog является Animal)
  • Интерфейс: объекты умеют что-то делать (Dog умеет ISaveable, ITrainable)

// Хорошая комбинация: базовый класс + интерфейсы
abstract class Item { public string Name { get; set; } }
interface IUsable   { bool Use(); }
interface IStorable { int StorageSize { get; } }

class Potion : Item, IUsable, IStorable
{
    public bool Use() { Console.WriteLine("Выпито!"); return true; }
    public int StorageSize => 1;
}

6. Пространства имён (namespace)

Пространство имён - способ организации и группировки кода. Предотвращает конфликты имён при наличии классов с одинаковыми именами.

// Объявление пространства имён
namespace GameCore.Inventory
{
    class Item
    {
        public string Name { get; set; }
    }
}

namespace GameCore.Enemies
{
    // Здесь тоже может быть класс Item - конфликта нет
    class Item
    {
        public int Damage { get; set; }
    }
}

7. Способы обращения к другому пространству имён

Через using - импорт

using System;                      // стандартная библиотека
using System.Collections.Generic;  // List, Dictionary и др.
using GameCore.Inventory;           // свой namespace

// Теперь можно писать без полного пути
Item sword = new Item();  // GameCore.Inventory.Item

Через полное имя (без using)

// Полный путь без using
GameCore.Inventory.Item sword = new GameCore.Inventory.Item();

Псевдоним (using alias)

using InvItem = GameCore.Inventory.Item;
using EnemyItem = GameCore.Enemies.Item;

InvItem  sword  = new InvItem();
EnemyItem arrow = new EnemyItem();
Namespace в Unity

В Unity каждый скрипт обычно живёт в пространстве имён, совпадающим с именем проекта или папки:

namespace MyGame.Player
{
    public class PlayerController : MonoBehaviour { }
}

UnityEngine - это тоже пространство имён, которое вы подключаете через using UnityEngine.


Для углублённого изучения

Полиморфизм

Полиморфизм - способность объектов разных типов реагировать на одно и то же сообщение (вызов метода) по-разному.

Полиморфизм - прямое следствие абстракции и наследования.

abstract class Weapon
{
    public string Name { get; }
    public Weapon(string name) => Name = name;

    public abstract void Attack(string target);
}

class Sword : Weapon
{
    public Sword() : base("Меч") { }
    public override void Attack(string target)
        => Console.WriteLine($"Меч рубит {target}!");
}

class Bow : Weapon
{
    public Bow() : base("Лук") { }
    public override void Attack(string target)
        => Console.WriteLine($"Лук стреляет в {target}!");
}

class Staff : Weapon
{
    public Staff() : base("Посох") { }
    public override void Attack(string target)
        => Console.WriteLine($"Посох кастует заклинание на {target}!");
}

// Полиморфизм в действии: разные объекты, один вызов
List<Weapon> weapons = new List<Weapon> { new Sword(), new Bow(), new Staff() };

foreach (Weapon w in weapons)
{
    w.Attack("Гоблин"); // каждое оружие атакует по-своему!
}
// Меч рубит Гоблина!
// Лук стреляет в Гоблина!
// Посох кастует заклинание на Гоблина!
Полиморфизм в Unity

В Unity это ключевой паттерн: MonoBehaviour.Update() вызывается у всех компонентов сцены. Каждый компонент реализует Update() по-своему - это полиморфизм в действии.


Pattern matching - сопоставление с образцом

Pattern matching - возможность C# проверять тип объекта прямо в условии и сразу получать типизированную переменную. Особенно полезен при работе с иерархиями классов.

Оператор is - проверка типа с объявлением переменной

// Классический способ через наследование:
class Unit { public string Name; }
class Player : Unit { public int Level; }
class Enemy  : Unit { public int Damage; }

void Greet(Unit unit)
{
    // Без pattern matching - два шага: проверка + приведение
    if (unit is Player)
    {
        Player p = (Player)unit;
        Console.WriteLine($"Игрок {p.Name}, уровень {p.Level}");
    }

    // С pattern matching - проверяем тип и получаем переменную за один шаг
    if (unit is Player p)
        Console.WriteLine($"Игрок {p.Name}, уровень {p.Level}");
    else if (unit is Enemy e)
        Console.WriteLine($"Враг {e.Name}, урон {e.Damage}");
}

Оператор is not - отрицание

// Проверка на null - современный читаемый способ
if (unit is not null)
    Console.WriteLine("Объект существует");

if (unit is not Player)
    Console.WriteLine("Это не игрок");

Switch с pattern matching

switch умеет сопоставлять по типу и добавлять условия через when:

void Describe(Unit unit)
{
    switch (unit)
    {
        case Player p when p.Level >= 10:
            Console.WriteLine($"Опытный игрок {p.Name}");
            break;
        case Player p:
            Console.WriteLine($"Новичок {p.Name}");
            break;
        case Enemy e:
            Console.WriteLine($"Враг с уроном {e.Damage}");
            break;
        case null:
            Console.WriteLine("Объект не задан");
            break;
    }
}
Порядок case важен

При pattern matching case проверяются сверху вниз. Более конкретные условия должны стоять раньше общих: case Player p when p.Level >= 10 - до case Player p.

Связь с полиморфизмом

Pattern matching - альтернативный инструмент для работы с разными типами. Полиморфизм (virtual/override) предпочтителен, когда поведение задаётся в самих классах. Pattern matching удобен, когда логика разветвления находится снаружи классов и добавить virtual метод нет возможности.


Итог урока


📋 Перейти к практическим заданиям урока 6

Материалы для дополнительного ознакомления :

Пространства имён
Виртуальные свойства и методы
Абстрактные классы
Интерфейсы
Применение интерфейсов