Наследование
Иерархия классов
Управлять большим количеством разрозненных классов довольно сложно. С этой проблемой можно справиться путем упорядочивания и ранжирования классов, то есть объединяя общие для нескольких классов свойства в одном классе и используя его в качестве базового.
Эту возможность предоставляет механизм наследования, который является мощнейшим инструментом ООП. Он позволяет строить иерархии, в которых классы-потомки получают свойства классов-предков и могут дополнять их или изменять. Таким образом, наследование обеспечивает важную возможность многократного использования кода. Написав и отладив код базового класса, можно, не изменяя его, за счет наследования приспособить класс для работы в различных ситуациях. Это экономит время разработки и повышает надежность программ.
Классы, расположенные ближе к началу иерархии, объединяют в себе общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных особенностей.
Итак, наследование применяется для следующих взаимосвязанных целей:
- исключения из программы повторяющихся фрагментов кода;
- упрощения модификации программы;
- упрощения создания новых программ на основе существующих.
Кроме того, наследование является единственной возможностью использовать объекты, исходный код которых недоступен, но в которые требуется внести изменения.
Класс в С# может иметь произвольное количество потомков и только одного предка. При описании класса имя его предка записывается в заголовке класса после двоеточия. Если имя предка не указано, предком считается базовый класс всей иерархии System.Object.
[ атрибуты ] [ спецификаторы ] class имя_класса [ : предки ]
тело класса
Листинг 8.1 . Класс Student, потомок класса Person
using System;
namespace WindowsFormsApplication3
{
class Person
{
public string name;
public int age;
public string profession;
public Person(string name)
{
this.name = name;
}
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public Person(string name, string profession)
{
this.name = name;
this.profession = profession;
}
public Person(string name, int age, string profession)
{
this.name = name;
this.age = age;
this.profession = profession;
}
}
class Student : Person
{
string number_group;
public Student(string name, int age, string profession, string number_group)
: base(number_group)
{
this.name = name;
this.age = age;
this.profession = profession;
this.number_group = number_group;
}
public string GetInformation()
{
string information;
information = "Имя: " + this.name + "; Возраст: " + this.age.ToString() + "; Профессия: " + this.profession + "; № группы: " + this.number_group;
return information;
}
}
}
В классе Student введено поле number_group, определен собственный конструктор. Все поля и свойства класса Person наследуются в классе Student.
Результат работы программы:
Имя: Миша; Возраст: 23; Профессия: Студент; № группы: 08-к-ПИ1
Имя: Иван; Возраст: 22; Профессия: Менеджер; № группы: нет
Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы. Порядок вызова конструкторов определяется приведенными далее правилами:
- Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса без параметров.
- Для иерархии, состоящей из нескольких уровней, конструкторы базовых классов вызываются, начиная с самого верхнего уровня. После этого выполняются конструкторы тех элементов класса, которые являются объектами, в порядке их объявления в классе, а затем исполняется конструктор класса. Таким образом, каждый конструктор инициализирует свою часть объекта.
- Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации (это продемонстрировано в конструкторах, вызываемых в операторах 1 и 2). Вызов выполняется с помощью ключевого слова base. Вызывается та версия конструктора, список параметров которой соответствует списку аргументов, указанных после слова base.
Поля, методы и свойства класса наследуются, поэтому при желании заменить элемент базового класса новым элементом следует явным образом указать компилятору свое намерение с помощью ключевого слова new.
Элементы базового класса, определенные как public, в производном классе доступны.
Важно понимать, что на этапе выполнения программы объект представляет собой единое целое, не разделенное на части предка и потомка.
Во время выполнения программы объекты хранятся в отдельных переменных, массивах или других коллекциях. Во многих случаях удобно оперировать объектами одной иерархии единообразно, то есть использовать один и тот же программный код для работы с экземплярами разных классов. Желательно иметь возможность описать:
- объект, в который во время выполнения программы заносятся ссылки на объекты разных классов иерархии;
- контейнер, в котором хранятся объекты разных классов, относящиеся к одной иерархии;
- метод, в который могут передаваться объекты разных классов иерархии;
- метод, из которого в зависимости от типа вызвавшего его объекта вызываются соответствующие методы.
Все это возможно благодаря тому, что объекту базового класса можно присвоить объект производного класса .
Абстрактный класс служит только для порождения потомков. Как правило, в нём задаётся набор методов, которые в каждом из потомков будут реализовываться по-своему. Абстрактный класс задаёт интерфейс всей иерархии. Абстрактный класс может содержать полностью определённые методы, помимо абстрактных.
Синтаксис:
abstract class AbsExample
{
public abstract void Print()
}
class Person:AbsExample
{
…
override public void Print()
{
C.W.(“Person {0} \ t “ , name);
}
}
class Student:AbsExample
{
override public void Print()
{
C.W.(“Group{0} \t”, group);
}
…
}
Если производный от абстрактного класс не переопределяет все абстрактные методы, то он также является абстрактным и описывается со спецификатором abstract.
Контрольные вопросы по теме «Иерархия классов»:
1. Дать определение наследованию.
2. Сколько предков может иметь класс в С#?
3. Сколько потомков может иметь класс в С#?
4. Синтаксис объявителя производного класса.
5. Наследуются ли конструкторы?
6. Наследуются ли поля, методы, свойства?
7. С помощью какого ключевого слова можно переопределить элемент базового класса?
8. Метод производного класса замещает метод базового класса. Как обратится к нему из метода производного класса?
9. Какой процесс называется ранним связыванием?
10. Какой процесс называется поздним связыванием?
11. Какие методы реализуют в С# процесс позднего связывании?
12. Синтаксис объявителя виртуальных методов.
13. При помощи какого ключевого слова переопределяется виртуальный метод в производном классе?
14. Синтаксис объявителя переопределенного виртуально метода в производном классе.
15. Какие ограничения накладываются на переопределённый виртуальный метод?
16. Нужно ли переопределять виртуальные методы в каждом производственном классе?
17. Какие классы называются абстрактными?
18. Может абстрактный класс содержать полностью определённые методы?