Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 22
Текст из файла (страница 22)
В настоящем разделе преследуется одна цель; представить наследование таким образом, чтобы можно было оценить его мощь. и при этом избежать злоупотребления им. Синтаксис определения класса уже был показан ранее. Базовый класс указывается после двоеточия, следующего за именем класса. В С№ класс имеет только один базовый класс (некоторые языки, например.
С++, поддерживают множественное наследование). Доступность членов Доступность членов является важным аспектом наследования, особенно, когда речь идет о доступности членов базового класса из производного класса. Любые общедоступные члены базового класса становятся общедоступными и в производном классе. Любые члены, помеченные модификатором ргогесгеб (защищенные), доступны только внутри объявляющего вх класса и его наследников.
Защищенные члены никогда не доступны извне определяющего вх класса или его наследников. Приватные (рта таТе) члены не доступны нигде, кроме определяющего нх класса. Поэтому, несмотря на то, что производный класс наследует все члены базового класса, включая и приватные, код в производном классе не имеет доступа к приватным членам, унаследованным от базового класса. Вдобавок защищенные внутренние (ргосессетт апсегпа1) члены также видимы всем типам, определенным внутри одной сборки. и классам-наследникам определившего их класса. Реальность состоит в том, что производный класс наследует все члены базового класса за исключением конструкторов экземпляра, статических конструкторов и деструкторов.
Как было показано, управлять доступностью всего класса в целом можно при его определении. Единственными вариантами доступа к типу класса являются ьптегпа1 и рсЬ11с. При использовании наследования действует правило, что тип базового класса должен быть доступен как минимум настолько же, как и производный класс. Рассмотрим следующий код: с1эзз А ( ргогесгео ьпк х; ) рэьгас с1азз В: А ( ) Этот код не скомпилируется, потому что класс А объявлен как аосегпа1 и не является настолько (как минимум) доступным, как производный от него класс В. Вспомните, что в отсутствие модификатора доступа класс имеет доступ ассегпэ1, поэтому класс А на самом деле является ьпгегсэ1.
Для того чтобы этот код компилировался, понадобится либо повысить класс А до уровня доступа Рэь11с, либо ограничить класс в доступом апсегпа1. К тому же обратите внимание, что для класса А допустимо быть роь11с, а для класса  — кл егпа1. 80 Глава 4 Неявные преобразования и полиморфизм Представлять наследование и то, что оно делает, можно несколькими способами. Первый и наиболее очевидный — наследование позволяет позаимствовать реализацию. Другими словами, можно унаследовать класс Р от класса А и повторно использовать реализацию класса А в классе Р. Потенциально это позволит сэкономить некоторую часть работы при определении класса Р.
Другое применение наследования — специализация, когда класс Р становится специализированной формОй КЛаССа А. Например, рассмотрим иерархию классов, показанную на рис. 4.1. Рие. 4РП Специализация прн наследовании Как видите, классы Вессапд1е и С1гс1е наследуются от класса Реоюегггсяларе. Другими словами, они являются специализациями класса яеотеггасяларе. Специализация бессмысленна без полиморфизма и виртуальных методов. Полиморфизм более подробно рассматривается в разделе "Наследование и виртуальные методы" далее в главе. На данный момент достаточно лишь в общих чертах разобраться, что означает это понятие.
Полиморфизм описывает ситуацию, когда тип, на который ссылается определенная переменная, может вести себя как (и в действительности быть) экземпляр другого )более специализированного] типа. В главе 5 рассматриваются отличия и сходства межлу интерфейсами и контрактами. На рис. 4.1 показан метод класса яеоюеггасяьаре по имени Ртам. Этот же метод присутствует и в Весгэпд1е, и в сагс1е. Такую модель можно реализовать следующим образом: рпЫас с1азз Яеоюесггсэлэре ( рпЫвс чгггпа1 чоап оган)) ( // Выполнить некоторое рисование по умолчанию ) рпЬ11с с1аээ Вессапд1е: Яеоиегг1сэларе ) рпЫас очегг1йе чо1п' Ртам)) уу Нарисовать прямоугольник ) ) рпЫас с1аээ С1гс1е: Яеоюесг1сзлэре ( Классы, структуры и обьекты 81 рчптьс очегг1т(е чо1т( Ргаэ О /т Нарисовать круг ) ) рпЬ1гс с1аяя ВпсгуРоапт рг1часе ятаттс чогп ОгаиЯЬяре( беопесг1сЯЬаре яЬаре япаре.Ргаи()т ) ятасгс чотт) Ма1п() ( С1гс1е стгс1е = пеи Сггс1е(); беоветггсЯЬаре яЬаре = стгс1ет Огаизпаре( яЬаре )т ОгяиБЬаре( стгс1е ) ) В методе Маго создается новый экземпляр Сагс1е.
Сразу после этого получаетсн ссылка типа беотетгасБЬаре на тот же объект. Это важный момент. Комиилнтор здесь неявно преобразует эту ссылку в ссылку на тип беоптегг1сБЬаре, позволяя использовать простое выражение присваивания. На самом деле, однако, она продолжает ссылатьсн на тот же объект Сагс1е. В этом суть специализации типа и автоматического преобразования, сопровождающего ее. Теперь рассмотрим оставшуюся часть кода метода маап.
После получении ссылки беоаес гасБЬаре на экземпляр Ст гс1е можно передать ее методу РгаиБЬаре, который не делает ничего кроме вызова метода Ргаы переданной ему фигуры. Однако ссылка на объект фигуры на самом деле указывает на С1гс1е. метод Ргэы определен как виртуальный, а класс Сагс1е переопределлет виртуальный метод, так что вызов Ргаы на ссылке беовесгхсБЬаре на самом деле приводит к вызову С1гс1е.
Оган. Это и есть полиморфизм в действии. Метод ОгяыЯЬаре не интересует, какой конкретный тип фигуры представллет переданный ему объект. То, с чем он имеет дело — это беотесгасБЬаре, а С1гс1е является типом беовесгасБЬаре, Вот почему наследование иногда называют отношением "тя-а" ("явллется"). В данном примере Кессапо1е лвлнетсл беопег г1сБЬаре и Скгс1е является беоыесгасБЬаре. Ключ к ответу на вопрос, когда наследование имеет смысл, а когда нет, лежит в применении отношения "(я-а" к существующему дизайну, Если класс 0 наследуется от класса В, и класс 0 семантически не является классом В, то для данного отношении наследование является неподходящим инструментом. Следует дать еще одно важное замечание по поводу наследования и возможности преобразования.
Выше упоминалось, что компилятор неявно преобразует ссылку на экземпляр Сагс1е в ссылку на экземпляр беопесгасБЬэре.Нелепо в данном случае означает, что код не должен делать ничего специального для выполнения такого преобразования, а под "чем-то специальным" обычно имеется в виду операция приведения.
Поскольку компилятор обладает способностью делать это на основе знания иерархии наследования, то может показаться, что можно и обойтись без получения ссылки на беовесгхсЯЬаре перед вызовом ОгаыБЬаре с экземпляром Сагс1е. На самом деле так оно и есть! Зто доказывает последняя строка метода Маап. Ссылку на экземпляр Схгс1е можно просто передать непосредственно методу РгаыБЬаре,и поскольку компилятор может неявно преобразовать ее в ссылку на тип беовегг1сБЬаре исхода из отношений наследования, он выполнит всю работу за вас.
Здесь снова проявляется всл мощь этого механизма. 82 Глава 4 Теперь можно передавать любой экземпляр объекта, производного от Сеогве Г г1 с Я паре. После того, как построенное программное обеспечение будет упаковано в коробку и помечено наклейкой "Версия 1", некто может потом заняться версией 2 и определить новые фигуры, унаследованные от Беоглесг1сяпаре, причем код Оганяйаре не потребует никаких изменений. Ему даже не понадобится ничего знать от новой специализации. Это могут быль Тгареяо1г), Бг(пате (специализации Неспяпд1е] или же Е111рве. Это не имеет значения до тех пор, пока классы фигур наследуются от сеогве г та сяьаре.
Сокрытие членов Исходя из приведенного в предыдущем разделе обсуждения, несложно заключить, что, несмотря на свою мощность, наследование может быть использовано некорректно. Когда программисты впервые узнают о наследовании, они склонны применять его слишком часто, создавая проекты и иерархические структуры, которые впоследствии трудно сопровождать. Важно отметить, что у наследования есть альтернативы, которые во многих случаях более оправданы.
Среди различного рода ассоциаций, возможных между классами в дизайне программной системы, наследование — самая жесткая из всех. Ближе к концу главы будут затронуты и другие проблемы, связанные с наследованием. Однако давайте немного забежим вперед и рассмотрим некоторые основные эффекты от наследования. Обратите внимание, что наследование расширяет функциональность, но не может ее исключать. Например, общедоступные методы базового класса доступны через экземпляры производного класса и классов, унаследованных от него. Удалить эти функциональные возможности из производного класса нельзя. Рассмотрим следующий код: рпЬ11с с1авв й ( рпв11с яояб Оозовегвьпд() ( Яуявет.Сопяо1е.нгьсе11пе( "й.позовеяп1пд" )г ) рпЬ11с с1зяв В: й ( рпЬ11с яоьс ОоЯотеяпапдЕ1яе() ( яуясеа.Сопво1е.кг1сеь1пе( "В.ОояовеяпьпдЕ1яе" ); ) ) рпЬ11с с1авв Епягугоьпя ( яваяяс яоьо Ма1п() В Ь = пез В (); Ь.Оозоаевп1пд(); Ь.












