Содержание
Иногда бывает необходимо создавать классы, экземпляры которого нельзя создавать. Такие классы называются абстрактными. В каком случае нам может потребоваться абстрактный класс? Например, в случае, когда набор каких-либо сущностей относятся к одной и той же области знаний, но могут иметь различные реализации одних и тех же свойств и методов. Для примера, представим себе такую задачу — нам необходимо разработать систему учёта сотрудников в учебном заведении. Студент и преподаватель — это два человека у которых может быть имя, фамилия. При этом у преподавателя есть звание, должность, какие-то свои характеристики, например, стаж работы. У студента тоже могут быть свои, характерные только для него свойства, например, курс на котором он учится, группа, средний балл по всем изучаемым дисциплинам и т.д. Таким образом, исходя из этих данных, можно выделить следующие важные для нас сущности — преподаватель и студент. Абстрактный класс в этом случае — человек, то есть этот класс будет чем-то общим между студентом и преподавателем. Теперь попробуем реализовать нашу задачу в коде 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("Вася", "Пупкин");
приведет к ошибке
Теперь используем ключевую возможность ООП — наследование и создадим производный от Person
класс студента.
class Student: Person { string Group { get; set; } public Student(string name, string family, string group) : base(name, family) { Group = group; } }
Здесь мы уже определили свой конструктор для класса и добавили для класса свое свойство — 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
переопределен, мы получим следующую ошибку:
Это произошло по тому, что для определения переменной 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
и, при этом, не имеют никакого функционала. Абстрактными могут быть:
При использовании абстрактных членов класса следует иметь в виду следующее:
- абстрактные члены классов не должны иметь модификатор
private
. - производный класс обязан переопределить и реализовать все абстрактные методы и свойства, которые имеются в базовом абстрактном классе. При переопределении в производном классе такой метод или свойство объявляются с модификатором
override
. - если класс имеет хотя бы один абстрактный член, то этот класс должен быть определен как абстрактный.
В качестве примера, сделаем в классе Person
абстрактным метод Display
. Как только вы пометите метод ключевым словом abstract, компилятор сразу сообщит вам следующее:
так как, как было сказано выше, абстрактные члены класса не должны иметь никакой функциональности, то есть абстрактный метод в классе должен выглядеть следующим образом:
abstract class Person { abstract public string Display(); }
Теперь для наших классов Student
и Teacher
появится сообщение от C#:
Переопределим и реализуем абстрактный метод в наших классах:
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
и объекты такого класса не могут быть созданы с использованием конструктора абстрактного класса. В абстрактном классе могут быть абстрактные члены (методы, свойства, индексаторы, события). При этом, производный от абстрактного класс должен переопределять все абстрактные члены, если производный класс не является также абстрактным.