Г. Шилдт - Полный справочник по C# (1160789), страница 53
Текст из файла (страница 53)
Наследование'281// Отображаем тип треугольника,public void showStyle() {Console.WriteLine("Треугольник " + style);Класс T r i a n g l e не скомпилируется, поскольку ссылка на члены width и h e i g h tвнутри метода a r e a () вызовет ошибку нарушения прав доступа.
Поскольку width иh e i g h t — закрытые члены, они доступны только для членов их собственного класса.На производные классы эта доступность не распространяется.Закрытый член класса остается закрытым в рамках этого класса. К немунельзя получить доступ из кода, определенного вне этого класса, включаяпроизводные классы.На первый взгляд может показаться, что невозможность доступа к закрытым членам базового класса со стороны производного — серьезное ограничение.
Однако этоне так, поскольку в С# предусмотрены возможности решения этой проблемы. Однаиз них — protected-члены, о которых пойдет речь в следующем разделе. Втораявозможность — использование открытых свойств и методов, позволяющих получитьдоступ к закрытым данным.
Как было показано в предыдущих главах, С#программисты обычно предоставляют доступ к закрытым членам класса посредствомоткрытых методов или путем превращения их в свойства. Перед вами — новая версиякласса TwoDShape, в котором бывшие члены width и h e i g h t стали свойствами.// Использование свойств для записи и чтения закрытых// членов класса.using System;// Класс двумерных объектов,class TwoDShape {double pri_width; // Теперь это private-член.double pri_height; // Теперь это private-член.// Свойства width и height,public double width {get { return pri_width; }set { pri_width = value; }public double height {get { return pri__height; }set { pri_height = value; }}public void showDimO {Console.WriteLine("Ширина и высота равны " +width + " и " + height);// Класс треугольников - производный от класса TwoDShape.class Triangle : TwoDShape {public string style; // Тип треугольника.// Метод возвращает значение площади треугольника,public double area() {return width * height / 2;282Часть I.
Язык С#// Отображаем тип треугольника,public void showStyleO {Console.WriteLine("Треугольник " + style);c l a s s Shapes2 {p u b l i c s t a t i c void MainO {T r i a n g l e t l = new T r i a n g l e ( ) ;T r i a n g l e t 2 = new T r i a n g l e ( ) ;t l . w i d t h = 4.0;t l . h e i g h t = 4.0;t l . s t y l e = "равнобедренный";t 2 . w i d t h = 8.0;t 2 . h e i g h t - 12.0;t 2 . s t y l e = "прямоугольный";Console.WriteLine("Информация о t l : " ) ;tl.showStyle();tl.showDim();Console.WriteLine ("Площадь равна " + tl.areaO);Console.WriteLine();Console.WriteLine("Информация о t2: " ) ;t2.showStyle();t2.showDim();Console.WriteLine("Площадь равна " + t2.area());Базовый и производный классы иногда называют суперклассом и подклассом. Этитермины пришли из программирования на языке Java.
Суперкласс в Java — это базовый класс в С#. Подкласс в Java — это производный класс в С#. Вероятно, вам приходилось слышать эти термины, но мы будем придерживаться стандартных С#терминов. В C++ также используются термины "базовый класс/производный класс".Использование защищенного доступаКак упоминалось выше, закрытый член базового класса недоступен для производного класса. Казалось бы, это означает, что, если производный класс должен иметьдоступ к члену базового класса, его нужно сделать открытым.
При этом придетсясмириться с тем, что открытый член будет доступным для любого другого кода, чтоиногда нежелательно. К счастью, таких ситуаций можно избежать, поскольку С# позволяет создавать защищенные члены. Защищенным является член, который открыт длясвоей иерархии классов, но закрыт вне этой иерархии.Защищенный член создается с помощью модификатора доступа p r o t e c t e d . Приобъявлении protected-члена он по сути является закрытым, но с одним исключением.
Это исключение вступает в силу, когда защищенный член наследуется. В этомслучае защищенный член базового класса становится защищенным членом производного класса, а следовательно, и доступным для производного класса. Таким образом,используя модификатор доступа p r o t e c t e d , можно создавать закрытые (для "внешГлава 11. Наследование283него мира") члены класса, но вместе с тем они будут наследоваться с возможностьюдоступа со стороны производных классов.Рассмотрим простой пример использования защищенных членов класса.// Демонстрация использования защищенных членов класса.using System;class В {protected int i, j ; // Закрыт внутри класса В,/ / н о доступен для класса D.public void set(int a, int b) {i = a;j = b;}public void show() {Console«WriteLine(i + " " + j) ;class D : В {int k; // Закрытый член.// Класс D получает доступ к членам i и j класса В.public void setk() {k = i* j ;p u b l i c void showk() {Console.WriteLine(k) ;c l a s s ProtectedDemo {p u b l i c s t a t i c void Main()D ob = new D();ob.set(2, 3);ob.show();ob.setk();ob.showk();{/ / OK, так как D "видит" В-члены i и j ./ / OK, так как D "видит" В-члены i и j ./ / OK, так как это часть самого класса D./ / OK, так как это часть самого класса D.Поскольку в этом примере класс в наследуется классом D и члены i и j объявленызащищенными в классе В (т.е.
с использованием модификатора доступа p r o t e c t e d ) ,метод s e t k ( ) может получить к ним доступ. Если бы члены i и j были объявлены вклассе в закрытыми, класс D не имел бы к ним права доступа, и программа не скомпилировалась бы.Подобно модификаторам p u b l i c и p r i v a t e модификатор p r o t e c t e d остается сосвоим членом независимо от реализуемого количества уровней наследования. Следовательно, при использовании производного класса в качестве базового для созданиядругого производного класса любой защищенный член исходного базового класса, который наследуется первым производным классом, также наследуется в статусе защищенного и вторым производным классом.284Часть I.
Язык С#Конструкторы и наследованиеВ иерархии классов как базовые, так и производные классы могут иметь собственные конструкторы. При этом возникает важный вопрос: какой конструктор отвечаетза создание объекта производного класса? Конструктор базового или конструкторпроизводного класса, или оба одновременно? Ответ таков: конструктор базового класса создает часть объекта, соответствующую базовому классу, а конструктор производного класса — часть объекта, соответствующую производному классу. И это вполнелогично, потому что базовый класс "не видит" или не имеет доступа к элементампроизводного класса. Поэтому их конструкции должны быть раздельными.
В предыдущих примерах классы опирались на конструкторы по умолчанию, создаваемые автоматически средствами С#, и поэтому мы не сталкивались с подобной проблемой.Но на практике большинство классов имеет конструкторы, и вы должны знать, каксправляться с подобной ситуацией.Если конструктор определяется только в производном классе, процесс созданияобъекта несложен: просто создается объект производного класса. Часть объекта, соответствующая базовому классу, создается автоматически с помощью конструктора поумолчанию. Например, рассмотрим переработанную версию класса T r i a n g l e , в которой определяется конструктор.
Здесь член s t y l e объявлен private-членом, поскольjcy теперь он устанавливается конструктором.// Добавление конструктора в класс Triangle.using System;// Класс двумерных объектов,class TwoDShape {double pri_width;// Закрытый член.double pri_height; // Закрытый член.*// Свойства width и height,public double width {get { return pri_width; }set { pri_width = value; }}public double height {get { return pri_height; }set { pri_height = value; }}public void showDim() {Console.WriteLine("Ширина и высота равны " +width + " и " + height);// Класс треугольников - производный от класса TwoDShape.class Triangle : TwoDShape {string s t y l e ; // Закрытый член.// Конструктор.public Triangle(string s, double w, double h) {width = w; // Инициализирует член базового класса.height = h; // Инициализирует член базового класса.Глава 11. Наследование285style - s;// Инициализирует член своего класса.// Метод возвращает значение площади треугольника,public double area() {return width * height / 2;// Отображаем тип треугольника,public void showStyleO {Console.WriteLine("Треугольник " + style);class Shapes3 {public static void Main() {Triangle tl = new Triangle("равнобедренный",4.0, 4.0);Triangle t2 - new Triangle("прямоугольный",8.0,12.0);Console.WriteLine("Информация о tl: " ) ;tl.showStyle();tl.showDim();Console.WriteLine ("Площадь равна " + tj..area());Console.WriteLine();Console.WriteLine("Информация о t2: " ) ;t2.showStyle();t2.showDim();Console.WriteLine("Площадь равна " + t2.area());Здесь конструктор класса T r i a n g l e инициализирует наследуемые им члены классаTwoDShape, а также собственное поле s t y l e .Если конструкторы определены и в базовом, и в производном классе, процесс создания объектов несколько усложняется, поскольку должны выполниться конструкторы обоих классов.
В этом случае необходимо использовать еще одно ключевое словоС# base, которое имеет два назначения: вызвать конструктор базового класса и получить доступ к члену базового класса, который скрыт "за" членом производного класса. Сначала рассмотрим первое назначение слова base.Вызов конструкторов базового классаПроизводный класс может вызывать конструктор, определенный в его базовомклассе, используя расширенную форму объявления конструктора производного классаи ключевое слово base.
Формат расширенного объявления таков:конструктор_производиого_класса (список_параметров) : base (список_аргументов) {// тело конструктора}Здесь с помощью элемента список_аргументов задаются аргументы, необходимыеконструктору в базовом классе.286Часть I. Язык С#Чтобы понять, как используется ключевое слово base, рассмотрим в следующейпрограмме еще одну версию класса TwoDShape. В ней определяется конструктор, который инициализирует свойства width и height.// Добавление конструкторов в класс TwoDShape.using System;// Класс двумерных объектов,class TwoDShape {double pri_width; // Закрытый член.double pri_height; // Закрытый член.// Конструктор класса TwoDShape.public TwoDShape(double w, double h) {width = w;height = h;}// Свойства width и height,public double width {get { return pri_width; }set { pri_width = value; }}public double height {get { return pri__height; }set { pri_height = value; }}p u b l i c void showDimO {Console.WriteLine("Ширина и высота равны " +width + " и " + h e i g h t ) ;// Класс треугольников, производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Вызываем конструктор базового класса,public Triangle(string s,double w,double h) : base(w, h) {style = s;// Метод возвращает площадь треугольника,public double area() {return width * height / 2;// Отображаем тип треугольника,public void showStyleO {Console.WriteLine("Треугольник " + style)Глава 11.
Наследование287class Shapes4 {public static void Main() {Triangle tl = new Triangle("равнобедренный", 4.0, 4.0);Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0);Console.WriteLine("Информация о tl: " ) ;tl.showStyle();tl.showDim();Console .WriteLine ("Площадь равна " + tl.areaO);Console.WriteLine();Console.WriteLine("Информация о t2: " ) ;t2.showStyle();t2.showDim();Console.WriteLine("Площадьравна" + t2.area());Здесь конструктор T r i a n g l e () вызывает "метод" base () с параметрами w и h, чтов действительности означает вызов конструктора TwoDShape (), который инициализирует свойства width и h e i g h t значениями w и h, соответственно. Класс T r i a n g l eбольше не инициализирует эти значения сам. Ему остается инициализировать толькоодно значение, уникальное для класса треугольников, а именно член s t y l e (тип треугольника).