Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Podbelsky_V_V_C_Bazovy_kurs.pdf
Скачиваний:
69
Добавлен:
02.06.2015
Размер:
1.73 Mб
Скачать

244

Г л а в а 1 3

 

 

13.3. Наследование классов

Наиболее богатым в отношении повторного использования кода и полиморфизма является отношение наследования классов. Для него используют название «является» (is-a). При наследовании объект производного класса служит частным случаем или специализацией объекта базового класса. Например, велосипед является частным случаем транспортного средства. Не проводя сравнительного анализа всех тонкостей возможных отношений между классами (этим нужно заниматься в специальном курсе, посвященном объектно-ориентированной методологии, см., например, [3]), покажем на примере рассматриваемых классов “точка на плоскости” и “окружность с заданным точкой центром” как реализуется наследование в языке C#. Итак класс Point будет базовым классом, а класс Circle сделаем его наследником, иначе говоря, производным от него.

Для объявления класса, который является наследником некоторого базового класса, используется следующий синтаксис:

модификаторыopt class имя_производного_класса :имя_базового_класса {операторы_тела_производного_класса}

Конструкция :имя_базового_класса в стандарте C# называется спецификацией базы класса.

Таким образом, класс Сircle как производный от базового класса Рoint можно определить таким образом (программа 13_04.cs):

class Circle : Point // Класс "окружность на плоскости"

{// Закрытое поле:

double rad;

// Радиус окружности

//Свойство для радиуса окружности: public double Rad { get { return rad; }

set { rad = value; } }

//Свойство для получения значения длины окружности:

public double Len { get { return 2 * rad * Math.PI; } }

//Свойство для центра окружности:

public Point Centre

{

Включение, вложение и наследование классов

245

 

 

get

{

Point temp = new Point(); temp.X = X;

temp.Y = Y; return temp;

}

set { X = value.X; Y = value.Y; }

}

public void display()

{

Console.WriteLine("Centre: X={0}, Y={1}; " + "Radius={2}, Length={3,6:f2}",

X, Y, rad, Len);

}

}

В производном классе Circle явно определены поле double rad, три уже рассмотренных свойства Rad, Len Centre и метод display(). По сравнению с предыдущими примерами класс Point не изменился. Он так же содержит два закрытых поля, задающих координаты точки, и два открытых свойства X, Y, обеспечивающих доступ к этим полям. В классе Point конструктор добавлен компилятором. Нет явного определения конструктора и в классе Сircle. Поэтому объекты класса Circle можно создавать только с умалчиваемыми значениями полей.

При наследовании производный класс “получает в наследство” все поля, свойства и методы базового класса, за исключением конструктора – конструктор базового класса не наследуется. Получив от базового класса его поля, методы и свойства, базовый класс может по-разному “распорядиться с наследством”. Поля базового класса непосредственно входят в число полей производного класса. Однако доступ к полям базового класса для методов, свойств и объектов производного класса разрешен не всегда. Закрытые поля свойства и методы базового класса недоступны для методов, свойств и объектов производного класса.

Открытые поля, методы, и свойства базового класса доступны для методов, свойств и объектов производного класса.

246

Г л а в а 1 3

 

 

В нашем примере класс Point имеет два открытых свойства, которыми можно пользоваться как внутри класса Circle так

иво внешнем мире, обращаясь к этим свойствам с помощью объектов класса circle. В методе display() производного класса выполняется непосредственное обращание к унаследованным свойствам X, Y.

Особое внимание в нашем примере с наследованием нужно обратить на свойство Circle.Centre. При агрегации

икомпозиции класса Point в класс Circle значением этого свойства служит ссылка на непосредственно существующий объект класса Point. В случае наследования в объекте класса Circle объекта класса Point нет – присутствуют только поля такого объекта и в классе Circle доступны открытые свойства класса Point. Поэтому для объявления в классе Circle свойства Centre объект класса Point приходится «реконструировать». В get-аксессоре явно создается временный объект класса Point, его полям присваиваются значения полей, унаследованных классом Circle от базового класса Point. Ссылка на этот временный объект возвращается как значение свойства Circle.Centre. Set-аксессор свойства Circle.Centre очень прост – используются унаследованные свойства X, Y класса Point.

Следующий фрагмент программы (см. 13_04.cs) демонстрирует возможности класса Сircle.

class Program

{

static void Main()

{

Circle rim = new Circle(); rim.X = 24;

rim.Y = 10; rim.Rad = 2; rim.display();

rim = new Circle(); rim.display();

}

}

Включение, вложение и наследование классов

247

 

 

свойствам и методам объекта класса Сircle, так и к свойствам объекта базового класса Рoint. Следующие результаты выполнения программы дополняют приведенные объяснения:

Centre: X=24, Y=10; Radius=2, Length= 12,57 Centre: X=0, Y=0; Radius=0, Length= 0,00

В языке UML предусмотрено специальное обозначение для отношения наследования классов. Два класса (рис. 13.3) изображаются на диаграмме отдельными прямоугольниками, которые соединены сплошной линией со стрелкой на одном конце. Стрелка направлена на базовый класс.

В методе Main() создан объект класса Сircle. Он ассоциирован

Рис. 13.4. Диаграмма к программе 13_04.cs с наследованием

со ссылкой rim, и с ее помощью осуществляется доступ как к

классов

248

Г л а в а 1 3

 

 

13.4. Доступность членов класса при наследовании

Члены базового класса, имеющие статус доступа private, как были недоступны для внешнего по отношению к объявлению базового класса мира, так и остаются закрытыми для базового класса. Члены базового класса, имеющие модификатор public, открыты для членов и объектов производного класса.

Вряде случаев необходимо, чтобы члены базового класса были доступны (открыты) для членов производного класса, но

вто же время были закрыты (недоступны) для объектов производного класса. В этом случае в базовом классе эти члены должны быть защищенными, т. е. объявлены с модификатором protected. Сразу же отметим, что если класс рассматривается вне наследования, то защищенные члены ничем не отличаются от закрытых. К защищенным членам класса нельзя обращаться извне объявления класса.

Впроизводном классе обычно вводятся новые члены, определяющие новое поведение и дополнительные характеристики объектов производного класса. Для новых (не унаследованных) членов производных классов имена выбираются произвольно.

Если в производном классе объявлен член, имя которого совпадает с именем какого-либо члена базового класса, то для их различения в производном классе используются уточненные имена:

this.имя_члена_производного_класса base.имя_члена_базового_класса

При внешних обращениях одноименные члены базового и производного класса различаются по типам объектов, для которых эти обращения выполнены.

Рассмотрим пример. Определим класс «круг» и производный от него класс «кольцо». Предположим, что у нас нет необходимости создавать объекты класса «круг», и он будет использоваться только как базовый для определения других классов. Тогда его определение может быть таким (Программа 13_05.cs):

class Disk // Класс круг

{

Включение, вложение и наследование классов

249

 

 

protected double rad; // Радиус круга

protected Disk(double ri) { rad = ri; } // Конструктор protected double Area{ get { return rad * rad * Math.PI;}

}

}

В классе Disk одно поле rad, задающее значение радиуса круга, конструктор общего вида и свойство Area, позволяющее получить значение площади круга. Параметр конструктора явно использован в теле конструктора, где он задает значение поля rad, т. е. определяет радиус круга. Все члены класса объявлены с модификатором protected. При таком определении класс вне наследования ни к чему не годен. Невозможно создать объект класса Disk – его конструктор защищенный (protected). Если убрать явно определенный конструктор, компилятор добавит открытый конструктор умолчания. Но и в этом случае пользы не видно – создав объект, нельзя будет обратиться к его полю или свойству.

Используем класс Disk в качестве базового в следующем объявлении:

class Ring : Disk // Класс кольцо

{

new double rad;

// Радиус внутренней

окружности

 

// Конструктор:

 

public Ring(double Ri, double ri) : base(Ri) { rad = ri; }

public new double Area

{ get { return base.Area - Math.PI * rad * rad; } } public void print()

{

Console.WriteLine("Ring: Max_radius={0:f2}, " + "Min_radius={1:f2}," + " Area={2:f3}", base.rad, rad, Area);

}

}

В производном классе Ring поле new double rad определяет значение внутренней окружности границы кольца. Радиус внешней границы определяет одноименное поле double rad, унаследованное из базового класса. Оба поля вне объявления

250

Г л а в а 1 3

 

 

класса Ring недоступны. Конструктор производного класса Ring объявлен явно, как открытый метод класса. У этого конструктора два параметра, позволяющие задавать значения радиусов границы кольца. В теле конструктора второй параметр double ri определяет внутренний радиус. В инициализаторе конструктора :base(Ri) выполнено явное обращение к конструктору базового класса Disk. Параметр конструктора Ri служит аргументом в этом обращении. Отметим, что для простоты не используются никакие проверки допустимости значений параметров.

Обратите внимание на то, что в объявление поля rad производного класса Ring входит модификатор new. Появление new обусловлено следующим соглашением языка C#. Имя члена производного класса может совпадать (вольно или по ошибке) с именем какого-либо члена базового класса. В этом случае имя члена производного класса скрывает или, говорят, экранирует соответствующее имя члена базового класса. При отсутствии модификатора new компилятор выдаёт сообщение с указанием совпадающих имён и предложением “Use the new keyword if hiding was intended”

– “Используйте служебное слово new, если экранирование запланировано”. Именно для того, чтобы удостоверить компилятор в преднамеренном совпадении имен, радиус внутренней окружности объявлен с модификатором new. То же самое сделано и при объявлении в классе Ring свойства Area, позволяющего получить площадь кольца. Кроме того, класс Ring унаследовал свойство с тем же именем из базового класса. В get-аксессоре свойства Area из класса Ring выполнено явное обращение base.Area к свойству базового класса Disk. Открытый метод print() позволяет вывести сведения об объекте класса Ring. Они выводятся как значения полей base.rad, rad и свойства Area. Отметим, что принадлежность членов rad и Area классу Ring можно подчеркнуть, если задать аргументы метода WriteLine() в таком виде:

Console.WriteLine("Ring: Max_radius={0:f2}, Min_ radius={1:f2},"+ "Area={2:f3}", base.rad, this.rad, this.Area);

В качестве иллюстрации возможностей класса Ring рассмотрим такой фрагмент программы:

class Program { static void Main() {

Ring rim = new Ring(10.0, 4.0);

Включение, вложение и наследование классов

251

 

 

rim.print();

}

}

Результат выполнения программы:

Ring: Max_radius=10,00, Min_radius=4,00, Area=263,894

Для иллюстрации доступности членов классов при наследовании удобно использовать схему, приведенную на рис. 13.5. Так как при изложении материала мы не затронули понятие сборки, то члены с модификатором internal на схеме не показаны. Стрелки на схеме обозначают возможность обращения (доступность) к членам с разными модификаторами.

Рис. 13.5. Доступность членов при наследовании классов

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]