Классы и объекты C#: абстрактные классы

В программировании бывают моменты, когда необходимо разработать класс, экземпляры которого нельзя создавать. Такие классы называются абстрактными. В каком случае нам может потребоваться абстрактный класс? Например, в случае, когда набор каких-либо сущностей относятся к одной и той же области знаний, но могут иметь различные реализации одних и тех же свойств и методов. Для примера, представим себе такую задачу — нам необходимо разработать систему учёта сотрудников в учебном заведении. Студент и преподаватель — это два человека у которых может быть имя, фамилия, отчество и так далее. При этом у преподавателя есть звание, должность, какие-то свои характеристики, например, стаж работы. У студента тоже могут быть свои, характерные только для него свойства, например, курс на котором он учится, группа, средний балл по всем изучаемым дисциплинам и т.д. Таким образом, исходя из этих данных, можно выделить следующие важные для нас сущности — преподаватель и студент. Абстрактный класс  в этом случае — человек, то есть этот класс будет чем-то общим между студентом и преподавателем. Теперь попробуем реализовать нашу задачу в коде C#.

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

Создадим наш первый абстрактный класс, который будет представлять в программе какого-либо человека:

abstract class Person
{
    public string Name { get; set; }
    public string Family { get; set; }

    public Person(string name, string family)
    {
        Name = name;
        Family = family;
    }

    public string Display()
    {
        return $"{Family} {Name}";
    }
}

У класса определено два свойства: имя (Name) и фамилия (Family), а также конструктор и метод Diasplay, возвращающий строку, содержащую фамилию и имя человека. Ключевое слово abstract говорит нам о том, что класс является абстрактным и мы не можем воспользоваться конструктором абстрактного класса напрямую (создать объект абстрактного класса). Например, вот такой вызов

Person person = new Person("Вася", "Пупкин");

приведет к ошибке

Ошибка CS0144 Не удается создать экземпляр абстрактного типа или интерфейса «Person»

Теперь используем ключевую возможность ООП — наследование и создадим производный от Person класс студента.

class Student: Person
{
    string Group { get; set; }
    
    public Student(string name, string family, string group) : base(name, family)
    {
        Group = group;
    }
}

Здесь мы уже определили свой конструктор для класса, который вызывает конструктор базового класса (обратите внимание на использование ключевого слова base в конструкторе) и добавили для класса свое свойство — Group (название группы, в которой учится студент). Теперь, мы можем создать экземпляр этого класса и, например, воспользоваться методом Diasplay:

Person student = new Student("Вася", "Пупкин", "ГВН-105");
Student student1 = new Student("Ваня", "Иванов", "ГВН-105");
Console.WriteLine(student.Display());
Console.WriteLine(student1.Display());

Обратите внимание, что в первой строке мы объявили переменную student как Person (наш абстрактный класс), однако создали объект класса-потомка (Student) и по факту в нашей переменной лежит именно объект класса Student. Во втором случае мы использовали уже класс-потомок для описания типа переменной. Теперь, в классе Student переопределим метод Display следующим образом:

public string Display(bool withGroup)
{
    if (withGroup)
        return string.Concat(new string[] { $"{Group} ", Display() });
    else
        return Display();
}

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

Console.WriteLine(student.Display()); //метод из абстрактного класса
Console.WriteLine(student1.Display(true));

Соответственно, класс преподавателя можно сделать вот таким:

class Teacher: Person
{
    public int Seniority { get; set; }
    
    public Teacher(string name, string family, int seniority) : base(name, family)
    {
        Seniority = seniority;
    }
}

Опять же, несмотря на то, что классы Student и Teacher — это вполне самостоятельные классы, описывающие разные сущности, в программе мы можем объявить их, используя абстрактный класс Person.

Использование методов наследника и предка

Используя наши разработанные классы, попробуйте сделать вот такой вызов метода Display():

Person student = new Student("Вася", "Пупкин", "ГВН-105");
Console.WriteLine(student.Display(true));

Несмотря на то, что метод Diaplay() у класса Student переопределен, мы получим следующую ошибку:

Ошибка CS1501 Ни одна из перегрузок метода «Display» не принимает 1 аргументов.

Это произошло по тому, что для определения переменной student мы использовали класс Person у которого действительно нет метода Diaplay, который бы принимал хотя бы один аргумент. Чтобы воспользоваться методом наследника мы должны указать компилятору, что у нас лежит по факту в переменной student:

string res = ((Student)student).Display(true);
Console.WriteLine(res);

строку, возвращаемую методом Display я для наглядности вынес отдельной строкой в коде. Перед переменной мы указали в скобках тип данных (Student). Если же мы хотим воспользоваться методом Display у предка, то здесь ничего указывать не требуется:

string res = student.Display();
Console.WriteLine(res);

Абстрактные члены класса

Абстрактные классы в C# моuen также иметь абстрактные члены классов, которые, также как и класс, определяются с помощью ключевого слова abstract и, при этом, не имеют никакого функционала. Абстрактными могут быть:

При использовании абстрактных членов класса следует иметь в виду следующее:

  1. абстрактные члены классов не должны иметь модификатор private.
  2. производный класс обязан переопределить и реализовать все абстрактные методы и свойства, которые имеются в базовом абстрактном классе. При переопределении в производном классе такой метод или свойство объявляются с модификатором override.
  3. если класс имеет хотя бы один абстрактный член, то этот класс должен быть определен как абстрактный.

В качестве примера, сделаем в классе Person абстрактным метод Display. Как только вы пометите метод ключевым словом abstract, компилятор сразу сообщит вам следующее:

Ошибка CS0500 «Person.Display()» не может объявить тело, потому что помечен как abstract.

так как, как было сказано выше, абстрактные члены класса не должны иметь никакой функциональности, то есть абстрактный метод в классе должен выглядеть следующим образом:

abstract class Person
 {
     abstract public string Display();
 }

Теперь для наших классов Student и Teacher появится сообщение от C#:

Ошибка CS0534 «Student» не реализует наследуемый абстрактный член «Person.Display()».

Переопределим и реализуем абстрактный метод в наших классах:

class Student: Person
{
    public override string Display()
    {
        return $"{Group} {Name} {Family}";
    }
}
class Teacher: Person
{
    public override string Display()
    {
        return $"{Name} {Family} Стаж: {Seniority}";
    }
}

теперь метод Display() переопределен (не путать с перегружен) и можно собрать нашу программу и проверить её работоспособность.

Отказ от реализации абстрактных членов

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

abstract class Anybody : Person
{ 
  //для такого класса переопределять метод Display() не требуется
}

Итого

В C# мы можем создавать некие общие для набора сущностей классы, которые называются абстрактными. Абстрактные классы помечаются ключевым словом abstract и объекты такого класса не могут быть созданы с использованием конструктора абстрактного класса. В абстрактном классе могут быть абстрактные члены (методы, свойства, индексаторы, события). При этом, производный от абстрактного класс должен переопределять все абстрактные члены, если производный класс не является также абстрактным.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии