6_Абстракция и пространства имён
Урок 6 : Абстракция и пространства имён
Абстракция позволяет работать с разными объектами через единый интерфейс, скрывая детали реализации. Пространства имён организуют код в крупных проектах.
1. Что такое абстракция в ООП
Абстракция - принцип ООП, при котором выделяются только существенные характеристики объекта, а детали реализации скрываются.
Проще говоря: вы знаете что делает объект, но не обязаны знать как именно.
// Пример из жизни: вы знаете, что у машины есть педаль газа.
// Вам не нужно знать, как работает двигатель внутри.
// В коде: вы знаете, что у любого оружия есть метод Attack().
// Реализация у меча и лука - разная, но вызываете одинаково.
В C# абстракция реализуется двумя главными инструментами:
- Абстрактные классы (
abstract class) - Интерфейсы (
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();
В 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 это ключевой паттерн: 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;
}
}
При pattern matching case проверяются сверху вниз. Более конкретные условия должны стоять раньше общих: case Player p when p.Level >= 10 - до case Player p.
Pattern matching - альтернативный инструмент для работы с разными типами. Полиморфизм (virtual/override) предпочтителен, когда поведение задаётся в самих классах. Pattern matching удобен, когда логика разветвления находится снаружи классов и добавить virtual метод нет возможности.
Итог урока
- Абстракция - скрываем детали, показываем суть
- Абстрактный класс - нельзя создать напрямую; задаёт шаблон для потомков
- Интерфейс - только контракт; класс может реализовывать несколько интерфейсов
virtual- метод можно переопределить;abstract- обязательно переопределить- Полиморфизм - разные объекты, один вызов, разное поведение
- Pattern matching:
is Тип var- проверка типа с объявлением переменной;is not- отрицание;switchсwhen- условия по типу и значению - Namespace - организует код, предотвращает конфликты имён
using- импортирует пространство имён для удобного обращения
📋 Перейти к практическим заданиям урока 6
Материалы для дополнительного ознакомления :
Пространства имён
Виртуальные свойства и методы
Абстрактные классы
Интерфейсы
Применение интерфейсов