Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_po_programmirovaniyu_3_semestr.doc
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
2.11 Mб
Скачать

Предотвращение наследования с помощью ключевого слова sealed.

Принцип наследования допускает неограниченную глубину иерархии наследования. Производный класс, являющийся наследником базового класса, может в свою очередь сам оказаться в роли базового класса. Однако не всегда продолжение цепочки предок–потомок может оказаться целесообразным. Поэтому, несмотря на всю эффективность и полезность наследования, иногда все же возникает потребность предотвратить его. (стр.367)

Если при разработке класса возникла ситуация, при которой дальнейшее совершенствование и переопределение возможностей класса в деле решения специфических задач окажется нежелательным (сколько можно заниматься переопределением функций форматирования), то класс может быть закрыт для дальнейшего наследования. Закрытие класса обеспечивается спецификатором sealed. При этом закрываться для наследования может как класс целиком, так и отдельные его члены.

sealed class X{…}

class Y:X // Наследование от класса X невозможно

{

}

А так закрывается для переопределения функция–член класса:

class X

{

sealed public void f(){…}

}

class Y:X

{

public void f(){…}// Наследование метода f(), объявленного в классе X запрещено!

}

Отношения между классами

Использование наследования позволяет реализовать множество самых разных отношений между классами. Однако всех их можно условно разделить на две большие группы: отношения, определяемые классическим наследованием, и отношения агрегирования.

Первая группа отношений основана на использовании классического наследования, когда один класс может рассматриваться как некоторая модификация другого класса. В результате, отношения между классами строятся по принципу ("is-a"). То есть один класс – родительский (главный), а второй – дочерний (подчиненный). В этом случае без участия родительского класса невозможно будет реализовать и дочерний класс. Таким образом, классы А и В находятся в отношении наследования, если, при объявлении класса В, класс А указан в качестве родительского класса. Такое определение позволяет устанавливать отношения между родительскими классами и их наследниками.

Вторая группа отношений между классами строится на использовании принципа вложенности, когда один класс содержит ("has a") в своем составе экземпляр другого класса. Отношения агрегирования (вложенности) реализуются путем размещения (локализации) класса или его экземпляра внутри владеющего класса и передачи (делегирования) части своих полномочий вложенному классу или его экземпляру. В этом случае владеющий класс, выглядит как контейнер, содержащий в себе экземпляры других классов которым он поручает выполнение части своих функциональных возможностей. Другими словами, если существуют классы для построения объектов, способных выполнять какую-то часть функций класса-контейнера, то он включает их в свой состав и делегирует им выполнение соответствующей части своих полномочий. Если же среди доступных классов нет подходящих, тогда класс контейнер может самостоятельно создать внутри себя такой класс (вложенный класс) и использовать его экземпляр в качестве своей внутренней переменной, которой делегируется выполнение части своих функций. Формальное определение отношения агрегирования можно выглядеть следующим образом: классы А и В находятся в отношении вложенности, если одним из полей класса В является класс А или его экземпляр. Данное определение позволяет задавать отношения агрегирования между классами в виде двух его форм: вложенности и владения.

Следует отметить, что отношения наследования и вложенности - являются транзитивными. Если класс В является наследником класса А, а класс С является наследником класса В, то класс С также является и наследником класса А. Если класс А или его экземпляр является составной (вложенной) частью класса В, а класс В или его экземпляр является составной (вложенной) частью класса С, то класс А или его экземпляр также является и составной (вложенной) частью класса С.

Вследствие транзитивности рассматриваемых отношений вводится понятие уровня. Прямые владельцы и их подчиненные, прямые родители и наследники относятся к соответствующему уровню 1. Для обозначения отношения между классами обычно используется терминология, заимствованная из естественного языка. Например, прямые классы-наследники часто называются дочерними классами. Непрямые родители называются предками, а их непрямые наследники - потомками. Или, например, прямые владельцы и непосредственные подчиненные.

На практике вполне могут встречаться довольно длинные цепочки отношений между классами и их экземплярами. Например, библиотечные классы, составляющие систему Microsoft Office, полностью построены на отношении вложенности. При программной работе с объектами Word можно начать с объекта, задающего приложение Word, и добраться до объекта, задающего отдельный символ в некотором слове некоторого предложения одного из открытых документов. Для выбора нужного объекта можно задать такую цепочку: приложение Word: коллекция документов - документ - область документа - совокупность абзацев - абзац - совокупность предложений - предложение - набор слов - слово - набор символов - символ. В этой цепочке каждому понятию соответствует свой класс библиотеки Microsoft Office, где каждая пара соседствующих классов связана отношением вложенности.

Классы библиотеки FCL связаны как отношением вложенности, так и отношением наследования. Длинные цепочки наследования и вложенности достаточно характерны для классов этой библиотеки.

При проектировании классов часто возникает вопрос, какое же отношение между классами нужно построить. Рассмотрим совсем простой пример двух классов - Square и Rectangle, описывающих квадраты и прямоугольники. Наверное, понятно, что эти классы следует связать скорее отношением наследования, чем вложенности; менее понятным остается вопрос, а какой из этих двух классов следует сделать родительским. Еще один пример двух классов, описывающих легковой автомобиль и человека. Какими отношениями должны быть связаны эти классы с другим классом, например, с классом владельца автомобиля? Может ли он быть наследником обоих классов? Найти правильные ответы на эти вопросы проектирования классов помогает понимание того, что отношение вложенности (или владения) задает отношение "имеет" ("has а"), а отношение наследования задает отношение "является" ("is a"). В случае классов квадрата и прямоугольника понятно, что каждый объект квадрата "является" прямоугольником, поэтому между их классами имеет место отношение наследования, и родительским классом является класс прямоугольника, а класс квадрата (как частный случай прямоугольника) является его потомком.

В случае автомобилей, людей и владельцев авто также понятно, что владелец "является" человеком и "имеет" автомобиль. Поэтому класс владельца автомобиля является владельцем объекта класса автомобиля и наследником класса человека.

Отношение вложенности

До сих пор мы рассматривали возможность выстраивания отношений между классами, основанные на использовании только классического наследования. Теперь остановимся на том как могут быть реализованы отношения вложенности между классами, в чем их отличительные особенности. Начнем с простого примера. Предположим, что нам нужен класс объектов, отображающих геометрическую линию. Для этого можно воспользоваться ранее созданным нами классом точки. Очевидно, что отношение прямого наследования ("is-a") между типами для описания класса точки (Point) и описания класса линии (Line) было бы возможным, но выглядело бы достаточно странно. Является ли точка линией? Вряд ли. Но ясно и другое, что какая-то связь между ними все-таки существует. Попробуем отобразить ее в следующем определении класса Line. Построим класс Line, выполняющий роль контейнера, с вложенными в него двумя экземплярами класса Point:

class Line

{

Point begin, end; // объекты начальной и конечной точек линии

public Line()

{

begin=new Point(100, 100);

end=new Point(200, 100);

}

public Line(int x1, int y1, int x2, int y2)

{

begin=new Point(x1, y1);

end=new Point(x2, y2);

}

public int GetLenght()

{

Math.Sqrt( Math.Pow(end.GetX()-begin.GetX(), 2) +

Math.Pow(end.GetY()-begin.GetX(), 2) );

}

public Move(int dx, int dy)// Делегирует функциональную возможность класса точки

{

begin.Move(dx, dy);

end.Move(dx, dy);

}

}

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

Теперь, после того как конструктор создал поле вложенного объекта, методы владеющего класса могут его использовать, вызывая доступные ему методы и поля вложенного экземпляра класса. Метод (Move) класса Line выполняет свою работу с помощью вызова методов begin.Move(dx, dy) и end.Move(dx, dy), используя сервис, поставляемый методом Move(dx, dy) класса Point..

В данном случае успешно используются объекты класса точки, вложенные в класс линии и используемые ею в качестве своих внутренних (локальных) переменных. Но нам уже известно, что поля данных в классах рекомендуется скрывать от внешнего использования. Поэтому для того, чтобы открыть внешнему миру функциональные возможности скрываемого вложенного объекта, требуется использовать так называемое делегирование. Делегирование означает добавление в класс-контейнер таких открытых методов, которые будут использовать функциональные возможности вложенных в него объектов.

Расширение понятия о владеющем классе

До сих пор мы рассматривали случай, когда класс-контейнер содержит поле, представляющее собой вложенный в него объект (экземпляр) другого класса. Несмотря на то, что это довольно частая, но далеко не единственная ситуация, когда класс использует возможности другого класса. Возможна ситуация, когда метод владеющего класса локально создает вложенный объект другого класса и вызывает его методы в собственных целях, но по завершении работы такого метода заканчивает свою жизнь и локальный объект. Еще одна возможная ситуация - когда объекты вообще не создаются ни конструктором, ни методами класса клиента, а объект владеющего класса вызывает статические методы вложенного объекта. Оба эти варианта демонстрируют следующие два метода класса B:

public class A

{

public A(string s, int v) { str = s; val = v; }

public string str; public int val; public void MethodA() {

Console.WriteLine( "Это класс A"); Console.WriteLine ("поле1 = {0}, поле2 = {1}", str, val);

}

public static void StatMethodA() {

string s1 = "Статический метод класса А"; string s2 = "Помните: 2*2 = 4"; Console.WriteLine(s1 + " ***** " + s2);

}

}

public class B

{

public void MethodB1()

{

A inner = new A("локальный объект А",77);

inner.MethodA();

}

public void MethodB2()

{

A.StatMethodA();

}

}

Следует заметить что можно предложить еще один вариант класса В, который обладал бы точно такими же функциональными возможностями, как и приведенный вариант. Например:

public class B

{

class A //Определение вложенного класса А

{

public A(string s, int v) { str = s; val = v; }

public string str; public int val; public void MethodA() {

Console.WriteLine( "Это класс A"); Console.WriteLine ("поле1 = {0}, поле2 = {1}", str, val);

}

public static void StatMethodA() {

string s1 = "Статический метод класса А"; string s2 = "Помните: 2*2 = 4"; Console.WriteLine(s1 + " ***** " + s2);

}

}

public void MethodB1()

{

//Создание локального объекта (экземпляра) вложенного класса А

A inner = new A("локальный объект А",77);

inner.MethodA(); //Вызов метода вложенного объекта класса А

}

public void MethodB2()

{

A.StatMethodA();//Вызов статического метода вложенного класса А

}

}

На основе приведенных примеров можно дать более расширенное определение владеющего класса (контейнера), которое можно сформулировать следующим образом: класс B называется владельцем класса A, если внутри класса B:

- определяется вложенный класс А;

- или создаются экземпляры класса A в виде полей или локальных переменных;

- или используются статические поля или методы класса А;

Рассмотри отношения, которые могут возникать между владеющими и вложенными классами.

Отношения между владеющими и вложенными классами

Что могут делать владеющие классы (классы-контейнеры) и что могут делать вложенные классы или вложенные объекты других классов? Вложенный класс имеет в своем распоряжении поля и методы и предоставляет их для использования своему владеющему классу. Владеющие классы или классы-контейнеры создают подчиненные им экземпляры других классов. Вызывая доступные им методы из полей подчиненных им объектов, они могут управлять их работой. При этом владеющие классы не могут ни повлиять на поведение подчиненных им объектов, ни изменить состава их функциональных возможностей.

Вложенные классы или экземпляры других классов интересны их владельцам только своей открытой частью, составляющей интерфейс класса. Но большая часть членов подчиненных классов обычно закрывается от вмешательства владеющих классов, поскольку им незачем вникать в детали их внутреннего представления и в особенности их реализации. Такое сокрытие информации вовсе не означает, что разработчики класса-контейнера не должны быть знакомы с тем, как все реализовано во вложенных в него объектов других классов, хотя иногда преследуется и такая цель. В общем случае сокрытие означает, что классы-контейнеры строят свою реализацию, основываясь только на интерфейсной части вложенных в них класса или их экземпляров. Встроенный в класс-контейнер класс или встроенный экземпляр класса закрывает свои поля и часть методов от своего владельца, задавая для них атрибут доступа private или protected. Но некоторые элементы своего класса-контейнера вложенный элемент может считать привилегированными, предоставляя им методы и поля, недоступные другим классам. В этом случае поля и методы, снабжаются атрибутом доступа internal, а классы вложенных элементов класса-контейнера с такими привилегиями должны принадлежать одной сборке.

Особенности использования владеющих и вложенных классов

При использовании классов-контейнеров и вложенных в них элементов часто приходится сталкиваться с необходимостью ответа на следующие два вопроса:

- Может ли вложенный элемент быть того же класса, что и его владелец или нет?

- Могут ли два класса быть одновременно и контейнерами и элементами, вложенными друг в друга?

Ответы на оба эти вопросы положительны, а подобные ситуации достаточно типичны и не являются какой-либо экзотикой.

Первая ситуация наиболее характерна для динамических структур данных, например, списков. Элемент односвязного списка имеет поле, представляющее ссылку на следующий элемент (такого же типа, как и текущий элемент) односвязного списка. Элемент двусвязного списка имеет два таких поля (ссылки предыдущий и последующий элементы списка). Но подобная ситуация характерна не только для рекурсивно определяемых структур данных. Рассмотрим еще один пример. В классе Person могут быть заданы два поля - Father и Mother, задающие родителей персоны, и массив Children. Понятно, что все эти объекты могут быть того же класса что и класс Person.

Достаточно часто встречается и такая ситуация, когда классы имеют поля, которые взаимно ссылаются друг на друга. Например, классы Мужчина и Женщина, первый из которых имеет поле "жена" класса Женщина, а второй - поле "муж" класса Мужчина.

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

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