Меню
Код
Код
#статьи
  • 7220

ООП. Часть 5. Наследование и ещё немного полиморфизма

Вы всё время пользуетесь результатами наследования, даже если не знаете этого. Рассказываем, как меньше дублировать код и что общего у всех классов.

Евгений Кучерявый

Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.


Как наследовать класс

Для начала создадим класс, от которого будем наследовать. Обычно его называют базовым или родительским:

class Vehicle
{
    public string name;
    public int speed;
    public int x;
    public int y;
 
    public void Move(Direction d)
    {
        switch(d)
        {
            case Direction.Forward:
                y += speed;
                break;
            
            case Direction.Backward:
                y -= speed;
                break;
 
            case Direction.Left:
                x -= speed;
                break;
 
            case Direction.Right:
                x += speed;
                break;
        }
    }
}

Этот класс (Vehicle) представляет собой транспортное средство, но пока у него есть только слишком общие свойства (название, координаты и скорость) и поведение (перемещение). Нам может понадобиться реализовать класс, который тоже относится к транспортным средствам, но более конкретным. Например, это будет автомобиль (Car).

Если мы хотим, чтобы класс Car наследовал поля и методы класса Vehicle, то при его объявлении после названия нужно поставить двоеточие и имя родительского класса:

class Car : Vehicle
{
    
}

Теперь объекты класса Car обладают всеми полями и методами класса Vehicle:

Vehicle a = new Vehicle() //Создание экземпляра класса Vehicle
{
    name = "Motorcycle",
    speed = 2,
    x = 5,
    y = 10
};
 
Car b = new Car() //Создание экземпляра класса Car
{ //Используем те же поля
    name = "Car",
    speed = 3,
    x = 15,
    y = 40
};
 
//Используем одинаковые методы для обоих классов
a.Move(Direction.Forward);
b.Move(Direction.Left);
 
 
a = b; //Мы можем привести дочерний класс к типу родительского
//При этом стоит учитывать, что функционал и поля дочернего класса перестанут работать для этого объекта

Внимание! Наследовать можно только от одного класса.

Добавление новых полей и методов

Чтобы добавить в дочерний класс новое поле или метод, нужно просто объявить их:

class Car : Vehicle
{
    public int horsePower = 1000;
    public void Beep()
    {
        Console.WriteLine("Beep!");
    }
}

Теперь объекты этого класса могут использовать как метод Move (), так и метод Beep (). То же самое касается и полей.

Наследование конструкторов

Допустим, у родительского класса есть конструктор, который принимает один аргумент:

public Vehicle(string name)
{
    this.name = name;
}

Все дочерние классы должны вызывать его в своих конструкторах, передавая аргумент того же типа. Для этого используется ключевое слово base:

public Car(string name, int horsePower)
    :base(name)
{
    this.horsePower = horsePower;
}

В скобках после base указывается аргумент, который нужно передать в родительский класс. При этом повторно описывать логику присваивания name не нужно.

Если вы не хотите ничего вызывать, то просто создайте в наследуемом классе пустой конструктор.

Переопределение методов

Часто бывает нужно, чтобы какой-то метод в дочернем классе работал немного иначе, чем в родительском. Например, в методе Move () для класса Car можно прописать условие, которое будет проверять, не кончилось ли топливо. Точно так же может появиться необходимость переопределить свойство.

Методы и свойства, которые можно переопределить, называются виртуальными. В родительском классе для них указывается модификатор virtual:

public virtual void GetInfo()
{
    Console.WriteLine($"Name: {name}\nSpeed: {speed}");
}

А в дочернем для переопределения используется модификатор override:

public override void GetInfo()
{
    Console.WriteLine($"Name: {name}\nSpeed: {speed}\n Horse power: {horsePower}");
}

Таким образом можно определить разную логику для разных классов. Это тоже можно считать полиморфизмом.

Наследование от класса Object

Несмотря на то что наследовать можно только от одного класса, существует также и класс Object, который является родительским для всех остальных. У него есть четыре метода:

  1. Equals () — проверяет, равен ли текущий объект тому, что был передан в аргументе.
  2. ToString () — преобразует объект в строку.
  3. GetHashCode () — получает числовой хеш объекта. Этот метод редко используется, потому что может возвращать одинаковый хеш для разных объектов.
  4. GetType () — получает тип объекта.

Любой из них также может быть переопределён или перегружен. Например, метод Equals () можно использовать, чтобы он проверял, равны ли поля объектов:

public bool Equals(Car obj)
{
    bool areEqual = false;
 
    if(obj.name == this.name && obj.horsePower == this.horsePower)
    {
        areEqual = true;
    }
 
    return areEqual;
}

В данном случае это именно перегрузка, потому что ни один из вариантов метода Equals () не принимал объект класса Car. Отсюда следует, что переопределить можно только метод с такими же принимаемыми аргументами.

Особенности наследования

Есть несколько особенностей, которые нужно знать при работе с наследованием:

  1. Наследовать можно только от класса, уровень доступа которого выше дочернего или равен ему. То есть публичный класс не может наследоваться от приватного.
  2. Дочерний класс не может обращаться к приватным полям и методам родительского. Поэтому нужно либо определять логику приватных компонентов в базовом классе, либо создавать публичные свойства и методы, которые будут своего рода посредниками.
  3. У дочернего класса может быть только один родительский, но у родительского может быть несколько дочерних.
  4. Нельзя наследовать от класса с модификатором static.
  5. Можно наследовать от класса, который наследует от другого класса. Но с этим лучше не злоупотреблять, потому что можно быстро запутаться в их взаимосвязях.

Чтобы лучше это усвоить, стоит попробовать поработать с каждой особенностью на практике и немного поэкспериментировать.

Домашнее задание

Создайте несколько классов персонажей: например, воин, лучник и маг.

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

У всех персонажей должен быть метод Attack (), при вызове которого у разных персонажей будут выводиться различные сообщения. Например, если атаковать будет маг, то мы должны увидеть сообщение, что он запустил огненный шар.

Заключение

С помощью наследования можно создавать множество полезных классов с общим поведением и свойствами, при этом не дублируя код. Однако это ещё не всё, что можно использовать, — в следующей статье вы узнаете про интерфейсы и абстрактные классы.

А чтобы на практике узнать, как используется ООП со всеми его особенностями, записывайтесь на курс «C#-разработчик с 0 до PRO». Вы попробуете разрабатывать на C# сайты и десктопные приложения, выжимая максимум из объектно-ориентированного программирования.

Курс

Профессия C#-разработчик


130 часов — и вы научитесь писать программы на языке, созданном Microsoft. Вы создадите 5 проектов для портфолио, даже если до этого никогда не программировали. После обучения — гарантированное трудоустройство.

Понравилась статья?
Да