Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
26
Добавлен:
29.02.2016
Размер:
69.59 Кб
Скачать

Конструкторы производных классов

В разделе 6.3 обсуждалось, как можно применять конструкторы с индивидуальными класса­ми. Возникает интересный вопрос о том, что случится, если вы станете определять собст­венные конструкторы для классов, являющихся частью иерархии, унаследованной от дру­гих классов, которые тоже могут иметь специальные конструкторы.

Предположим, что ни для одного из своих классов вы не определили никаких явных конструкторов. Это значит, что компилятор создаст для них конструкторы без параметров по умолчанию. При этом достаточно много чего происходит такого, что скрыто от ваших глаз, но компилятор может гарантировать, что по всей иерархии классов каждое поле бу­дет инициализировано своим значением по умолчанию. Когда вы добавляете собственный конструктор, то берете на себя управление процессом конструирования, и вам следует за­ботиться о том, чтобы не сделать ничего такого, что помешает пройти конструированию гладко по всей иерархии классов.

Возможно, вас удивит, почему возникают какие-то особые проблемы с порожденными классами. Причина в том, что при создании экземпляра производного класса работает более одного конструктора. Конструктора класса, экземпляр которого создается, недоста­точно для полной инициализации  также должны быть вызваны конструкторы всех его базовых классов. Именно поэтому мы говорим о конструировании через иерархию.

Чтобы увидеть, почему должен быть вызван конструктор базового класса, рассмотрим пример программы, моделирующей работу компании-оператора сотовой связи под назва­нием MortimerPhones.

Пример включает абстрактный базовый класс GenericCustomer, представляющий любого клиента. Существует также неабстрактный класс Nevermore60Customer, который представляет любого заказчика, подключенного к тарифному плану Nevermore60. Все за­казчики имеют имя, представленное приватным полем. В режиме Nevermore60 первые несколько минут разговора заказчика оцениваются по повышенной расценке, что вызы­вает необходимость в поле highCostMinutesUsed, указывающем, сколько именно минут разговора по повышенной расценке использовано.

Определение классов показано ниже.

abstract class GenericCustomer

{

private string name; // прочие методы

}

class Nevermore60Customer: GenericCustomer

{

private uint highCostMinutesUsed;

// прочие методы

}

Мы не будем сейчас думать о том, как могут быть реализованы остальные методы этих классов, а сосредоточим внимание только на процессе конструирования. Если вы просмот­рите коды примеров для этой главы, то обнаружите, что определения этих классов вклю­чают только конструкторы.

Посмотрим, что произойдет, если использовать операцию new для создания экземпля­ра класса Nevermore60Customer:

GenericCustomer customer = new Nevermore60Customer();

Понятно, что оба поля-члена  и name, и highCostMinutesUsed  должны быть ини­циализированы при инициализации customer. Если вы не применяете своих собственных конструкторов, а полагаетесь на конструкторы по умолчанию, то можно ожидать, что name будет инициализировано значением null, a highCostMinutesUsed  нулем. Рассмотрим подробно, что именно произойдет на самом деле.

Поле highCostMinutesUsed не вызывает проблем: конструктор Nevermore60 Customer по умолчанию инициализирует это поле нулем.

А как насчет name? Если посмотреть на определения классов, станет понятно, что кон­структор класса Nevermore60Customer не может инициализировать это значение". Это поле объявлено приватным, значит, классы-наследники не имеют доступа к нему. То есть конструктор Nevermore60Customer по умолчанию просто не знает о его существовании. Только код функций-членов GenericCustomer имеет доступ к этому полю. Из этого сле­дует, что если поле name должно быть инициализировано, то это может сделать какой-то из конструкторов GenericCustomer. Вне зависимости от того, насколько велика иерар­хия классов, то же самое требование распространяется на все классы-предки  вплоть до System.Object.

Понимая все это, мы можем посмотреть, что именно происходит при создании каждо­го экземпляра порожденного класса. Предполагая сквозное использование конструкторов по умолчанию, компилятор сначала выбирает конструктор того класса, экземпляр кото­рого он пытается создать, в данном случае  Nevermore60Customer. Первое, что делает конструктор по умолчанию Nevermore60Customer  пытается вызвать конструктор по умолчанию своего непосредственного базового класса  GenericCustomer. Затем конст­руктор GenericCustomer пытается вызвать конструктор своего базового класса — System.Object. Класс System.Object не имеет базового класса, поэтому его конструктор просто выполняется и возвращает управление конструктору GenericCustomer. Этот конструктор выполняется и, прежде чем вернуть управление конструктору Nevermore60Customer, ини­циализирует name значением null. В свою очередь, конструктор Nevermore60Customer выполняется, инициализируя highCostMinutesUsed нулем, после чего завершается. К этому моменту экземпляр успешно сконструирован и инициализирован.

В результате имеем последовательный вызов конструкторов всех классов иерархии, начиная с System.Object и заканчивая инициализируемым классом. Обратите внима­ние, что в этом процессе каждый конструктор инициализирует поля собственного класса. Именно так все обычно должно работать, и при добавлении собственных конструкторов вы должны стараться следовать этому принципу.

Обратите внимание на последовательность, в соответствие с которой все происходит. Всегда имеется конструктор базового класса, который вызывается первым. Это значит, что нет проблем для конструктора базового класса вызвать любые методы, свойства и обра­титься к любым другим членам базового класса, к которым ему открыт доступ, поскольку базовый класс уже сконструирован и его поля инициализированы. Кроме того, это значит, что если классу-наследнику "не нравится" то, как инициализирован базовый класс, он мо­жет изменить начальные значения данных, к которым у него есть доступ. Однако хорошая практика программирования почти наверняка означает, что если вы сможете, то попытае­тесь предотвратить возникновение подобных ситуаций и доверите конструктору базового класса иметь дело с его собственными полями.

Теперь, зная как работает процесс конструирования, можно поэкспериментировать с ним, добавляя собственные конструкторы.

Соседние файлы в папке 04 Наследование