Содержание
В программировании бывают моменты, когда необходимо разработать класс, экземпляры которого нельзя создавать. Такие классы называются абстрактными. В каком случае нам может потребоваться абстрактный класс? Например, в случае, когда набор каких-либо сущностей относятся к одной и той же области знаний, но могут иметь различные реализации одних и тех же свойств и методов. Для примера, представим себе такую задачу — нам необходимо разработать систему учёта сотрудников в учебном заведении. Студент и преподаватель — это два человека у которых может быть имя, фамилия, отчество и так далее. При этом у преподавателя есть звание, должность, какие-то свои характеристики, например, стаж работы. У студента тоже могут быть свои, характерные только для него свойства, например, курс на котором он учится, группа, средний балл по всем изучаемым дисциплинам и т.д. Таким образом, исходя из этих данных, можно выделить следующие важные для нас сущности — преподаватель и студент. Абстрактный класс в этом случае — человек, то есть этот класс будет чем-то общим между студентом и преподавателем. Теперь попробуем реализовать нашу задачу в коде 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;
}
}
Здесь мы уже определили свой конструктор для класса, который вызывает конструктор базового класса (обратите внимание на использование ключевого слова 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 переопределен, мы получим следующую ошибку:
Это произошло по тому, что для определения переменной 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 и объекты такого класса не могут быть созданы с использованием конструктора абстрактного класса. В абстрактном классе могут быть абстрактные члены (методы, свойства, индексаторы, события). При этом, производный от абстрактного класс должен переопределять все абстрактные члены, если производный класс не является также абстрактным.