Наш проект с игрой начинает приобретать настоящую форму! Прототип, который мы написали, уже выглядит многообещающе. Мы сидим в ожидании того, когда покажем его руководству и получим дальнейшие указания, но за соседним столом сидящий разработчик
Фёдор не смог удержаться от того, чтобы заглянуть в наш проект и подметить пару важных моментов.
Он внимательно посмотрел на создание объектов и сказал:
Твой код полностью рабочий, и я даже могу создавать воинов и магов на основе твоих классов, но что будет, если в каждом классе будет гораздо больше полей, например, 50? А если ещё и объектов станет много — неужели мне нужно писать 50 строчек, задавая каждое поле вручную? Процесс создания объектов сейчас выглядит слишком сложным.
И действительно, если мы добавим много новых полей и объектов, это станет неудобно, и создание объектов станет очень громоздким. Сейчас при создании, например, магов нам приходится обращаться к каждому полю и задавать его значение.
// Создаем магов
Mage mage1 = new Mage();
mage1.Name = "Альберт";
mage1.Mana = 20;
Mage mage2 = new Mage();
mage2.Name = "Белла";
mage2.Mana = 30;
Задача: Вы решаете улучшить систему создания объектов. Как бы вы предложили упростить процесс создания магов и воинов внутри метода Main, чтобы упростить жизнь другим разработчикам, которые будут создавать объекты на основе вашего класса?
Подсказка: Возможно, логику задания значения для каждого поля стоит перенести в сам класс. Вам может помочь метод, который принимает несколько аргументов.
Наше решение стало короче, но всё ещё есть проблема.
Mage mage1 = new Mage();
mage1.Construct("Альберт", 20);
Warrior warrior1 = new Warrior();
warrior1.Construct("Эдвард", 100);
В этом коде мы создаём объект, а затем отдельно вызываем метод, чтобы заполнить поля. Это уже лучше, чем задавать каждое значение по отдельности, но всё же не идеально. Такой метод можно случайно забыть вызвать, и тогда поля нашего класса вообще не будут заполнены.
Хотелось бы, чтобы метод вызывался сразу при создании объекта, без необходимости отдельно обращаться к нему. Так мы точно не забудем указать необходимые данные.
Как раз для решения этой проблемы в языке C# используется конструктор.
Давайте, следуя определению, напишем конструктор для класса Mage:
public class Mage
{
public string Name;
public int Mana;
// Конструктор класса
public Mage(string name, int mana)
{
Name = name;
Mana = mana;
}
public void CastAttackSpell()
{
if (Mana >= 5)
{
Mana -= 5;
Console.WriteLine($"{Name} использует заклинание высшего круга магии. (Мана уменьшена на 5)");
}
else
{
Console.WriteLine($"Маг {Name} не может атаковать, не хватает маны");
}
}
public void RestoreMana(int amount)
{
if (amount < 0)
{
Console.WriteLine("Нельзя восстановить отрицательное количество маны.");
Mana += 0;
}
else
{
Mana += amount;
if (Mana > 100)
{
Mana = 100;
}
Console.WriteLine($"{Name} восполнил ману на {amount}. Текущая мана: {Mana}.");
}
}
}
И для класса Warrior:
public class Warrior
{
public string Name;
public int Stamina;
// Конструктор класса
public Warrior(string name, int stamina)
{
Name = name;
Stamina = stamina;
}
public void Attack()
{
if (Stamina >= 5)
{
Stamina -= 5;
Console.WriteLine($"{Name} наносит сильный удар. (Выносливость уменьшена на 5)");
}
else
{
Console.WriteLine($"Воин {Name} устал и не может атаковать");
}
}
}
Теперь для создания объектов с заполненными полями нам достаточно передать нужные значения при создании объекта. Порядок передачи значений важен!
internal class Program
{
static void Main(string[] args)
{
// Создаем объекты магов
Mage mage1 = new Mage("Альберт", 20);
Mage mage2 = new Mage("Белла", 30);
mage1.CastAttackSpell();
mage2.CastAttackSpell();
// Создаем воина
Warrior warrior1 = new Warrior("Эдвард", 100);
warrior1.Attack();
}
}
Посмотрите, какая красота! Вы можете посчитать, насколько сократился Ваш код при работе с объектами по сравнению с прошлым уроком.
Было:
Mage mageAlbert = new Mage();
mage1.Name = "Альберт";
mage1.Mana = 20;
Стало:
Mage mage1 = new Mage("Альберт", 20);
Иногда при создании объектов мы понимаем, что у многих из них будет одинаковое повторяющееся значение. Например, мы можем решить, что по умолчанию у всех магов одинаковое количество маны — 50. Тогда мы можем задать это значение в конструкторе как значение параметра по умолчанию:
// Устанавливаем значение по умолчанию для магической энергии
public Mage(string name, int mana = 50)
{
Name = name;
Mana = mana;
}
Теперь при создании магов мы можем не передавать второй параметр, и все маги будут создаваться с магической энергией равной 50 по умолчанию. Если же необходимо указать иное значение, параметр можно передать явно:
Mage mage1 = new Mage("Merlin"); // Мана по умолчанию равна 50
Mage mage2 = new Mage("Gandalf", 80); // Мана установлена в 80
Это правило касается не только конструкторов, но и любых других методов, которые вы будете создавать (конструктор по сути и есть метод, только особенный).
public Mage(string name, int mana = 50) // верно
public Mage(int mana = 50, string name) // ошибка компиляции
На самом деле конструктор у нас уже был с самого начала! Мы всегда его вызывали, только он был пустым. Посмотрите: мы всегда создавали объект так:
Mage mage1 = new Mage();
Мы постоянно использовали ключевое слово new, и, как мы знаем, оно выделяет память и создаёт объект. Но у него есть ещё одна важная задача — оно вызывает конструктор класса.
Но у нас же не было конструктора класса!
Да, явно мы его не писали. Однако в C# есть важная особенность: если в классе не указан конструктор, компилятор создаёт его автоматически.
Код урока: Конструктор**
Конструктор — это специальный метод в классе, который вызывается автоматически при создании объекта. Имя конструктора всегда совпадает с именем класса. Конструктор не имеет возвращаемого значения (даже void не указывается).
Конструктор по умолчанию — конструктор без параметров, который компилятор создаёт автоматически, если в классе не объявлен ни один конструктор явно. Инициализирует поля значениями по умолчанию (0 для чисел, null для строк).
Параметризованный конструктор — конструктор, принимающий один или несколько аргументов для инициализации полей объекта.
Ключевое слово new выделяет память для нового объекта и вызывает конструктор класса, передавая в него аргументы.
Если в классе создан хотя бы один конструктор с параметрами, конструктор по умолчанию автоматически не генерируется.
Параметры со значениями по умолчанию всегда должны идти в конце списка параметров конструктора.