Иерархия классов
Управлять большим количеством разрозненных классов довольно сложно. С этой проблемой можно справиться путем упорядочивания и ранжирования классов, то есть объединяя общие для нескольких классов свойства в одном классе и используя его в качестве базового.
Эту возможность предоставляет механизм наследования, который является мощнейшим инструментом ООП. Он позволяет строить иерархии, в которых классы-потомки получают свойства классов-предков и могут дополнять их или изменять. Таким образом, наследование обеспечивает важную возможность многократного использования кода. Написав и отладив код базового класса, можно, не изменяя его, за счет наследования приспособить класс для работы в различных ситуациях. Это экономит время разработки и повышает надежность программ.
Классы, расположенные ближе к началу иерархии, объединяют в себе общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных особенностей.
Итак, наследование применяется для следующих взаимосвязанных целей:
- исключения из программы повторяющихся фрагментов кода;
- упрощения модификации программы;
- упрощения создания новых программ на основе существующих.
Кроме того, наследование является единственной возможностью использовать объекты, исходный код которых недоступен, но в которые требуется внести изменения.
Класс в С# может иметь произвольное количество потомков и только одного предка. При описании класса имя его предка записывается в заголовке класса после двоеточия. Если имя предка не указано, предком считается базовый класс всей иерархии 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. Может абстрактный класс содержать полностью определённые методы?