Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MetodUkaz_VP_S.docx
Скачиваний:
16
Добавлен:
11.03.2015
Размер:
14.12 Mб
Скачать

Теоритические сведения

Наследование

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

Примечание: Структуры не поддерживают наследование, но они могут реализовывать интерфейсы.

Концептуально, производный класс является специализацией базового класса. Например, при наличии базового класса Animal, возможно наличие одного производного класса, который называется Mammal, и еще одного производного класса, который называется Reptile. Mammal является Animal и Reptile является Animal, но каждый производный класс представляет разные специализации базового класса.

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

  • Абстрактные и виртуальные методы

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

  • Абстрактные базовые классы

Можно объявить класс как абстрактный, если необходимо предотвратить прямое создание экземпляров с помощью ключевого слова new. При таком подходе класс можно использовать, только если новый класс является производным от него. Абстрактный класс может содержать один или несколько сигнатур методов, которые сами объявлены в качестве абстрактных. Эти сигнатуры задают параметры и возвращают значение, но не имеют реализации (тела метода). Абстрактному классу необязательно содержать абстрактные члены; однако, если класс все же содержит абстрактный член, то сам класс должен быть объявлен в качестве абстрактного. Производные классы, которые сами не являются абстрактными, должны предоставить реализацию для любых абстрактных методов из абстрактного базового класса.

  • Доступ производного класса к членам базового класса

Из производного класса можно получить доступ к открытым, защищенным, внутренним и защищенным внутренним членам базового класса. Хотя производный класс и наследует закрытые члены базового класса, он не может получить доступ к этим членам. Однако все эти закрытые члены все же присутствуют в производном классе и могут выполнять ту же работу, что и в самом базовом классе. Например, предположим, что защищенный метод базового класса имеет доступ к закрытому полю. Это поле должно присутствовать в производном классе для правильной работы унаследованного метода базового класса.

  • Предотвращение дальнейшего наследования

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

  • Скрытие производного класса членов базового класса

Производный класс может скрывать члены базового класса путем объявления членов с тем же именем и сигнатурой. Модификатор new может использоваться, чтобы явно указать, что член не предназначен, чтобы быть переопределением базового члена. Использование new не является обязательным, но при отсутствии использования new будет сгенерировано предупреждение компилятора.

Ниже иллюстрируется класс WorkItem, представляющий рабочий элемент в бизнес-процессе. Подобно всем классам, он является производным от System.Object и наследует все его методы. В WorkItem имеется пять собственных членов. Сюда входит конструктор, поскольку конструкторы не наследуются. Класс ChangeRequest наследуется от WorkItem и представляет конкретный вид рабочего элемента. ChangeRequest добавляет еще два члена к членам, унаследованным от WorkItem и Object. Он должен добавить собственный конструктор, и он также добавляет originalItemID. Свойство originalItemID позволяет связать экземпляр ChangeRequest с исходным объектом WorkItem, к которому применен запрос на изменение.

Рис 7.1Наследование классов

В этом рисунке показано, как выражаются в C# отношения между классами, продемонстрированные в предыдущем примере (серым цветом отмечены собственные члены класса, а жёлтым – унаследованные).

В следующем примере также показано, как WorkItem переопределяет виртуальный метод Object.ToString, и как класс ChangeRequest наследует реализацию WorkItemметода.

// WorkItem неявно наследуется от класса Object

public class WorkItem

{

// Статическое поле currentID идентификатор последнего созданного WorkItem

private static int currentID;

//Характеристики

protected int ID { get; set; } //ID рабочего элемента

protected string Title { get; set; } //Название рабочего элемента

protected string Description { get; set; } //Описание рабочего элемента

protected TimeSpan jobLength { get; set; } //Длина рабочего элемента (Что такое TimeSpan прочтите на MSDN)

// Конструктор по-умолчанию

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

// конструктор базового, из-за этого конструктор по-умолчанию базового класса вызовется неявно

public WorkItem()

{

ID = 0;

Title = "Название по-умолчанию";

Description = "Описание по-умолчанию";

jobLength = new TimeSpan();

}

// Конструктор с тремя параметрами

public WorkItem(string title, string desc, TimeSpan joblen)

{

this.ID = GetNextID();

this.Title = title;

this.Description = desc;

this.jobLength = joblen;

}

// Статический конструктор для инициализации статического поля. Этот

// конструктор вызовется один раз, автоматически, как только будет создан

// первый экземпляр WorkItem или ChangeRequest

// или если будет обращение к статическому полю через тип(без создания экземпляра)

static WorkItem()

{

currentID = 0;

}

protected int GetNextID()

{

// currentID это статическое поле. Оно инкрементируется каждый раз

// когда создается объект типа WorkItem.

return ++currentID;

}

// Этот метод позволяет обновить информацию о названии и временной характеристике

// рабочего элемента

public void Update(string title, TimeSpan joblen)

{

this.Title = title;

this.jobLength = joblen;

}

// Переопределение виртуального метода ToString класса Object

public override string ToString()

{

return String.Format("{0} - {1}", this.ID, this.Title);

}

}

// ChangeRequest наследуется от WorkItem и добавляет поле (originalItemID)

// а также два конструктора

public class ChangeRequest : WorkItem

{

protected int originalItemID { get; set; }

// Конструкторы. Так как ни один конструктор производного класса не вызывает

// конструктор базового класса явно(с использованием ключевого слова base),

// то неявно вызывается конструктор по-умолчанию базовго класса.

// Базовый класс обязан содержать конструктор по умолчанию

// Конструктор по умолчанию

public ChangeRequest() { }

// Конструктор с четырьмя параметрами

public ChangeRequest(string title, string desc, TimeSpan jobLen,

int originalID)

{

// Эти поля и метод унаследованы от базового класса

// А доступ к ним открыт, так как в базовом классе они объявлены как protected

this.ID = GetNextID();

this.Title = title;

this.Description = desc;

this.jobLength = jobLen;

// А это поле уже часть класса ChangeRequest, но не WorkItem

this.originalItemID = originalID;

}

}

class Program

{

static void Main()

{

// Создание экземпляра WorkItem используя конструктор с 3 параметрами

WorkItem item = new WorkItem("Исправить ошибки",

"Исправляет все ошибки в моей части кода",

new TimeSpan(3, 4, 0, 0));

// Создание экземпляра ChangeRequest используя конструктор с 4 параметрами

ChangeRequest change = new ChangeRequest("Изменить дизайн базового класса",

"Add members to the class",

new TimeSpan(4, 0, 0),

1);

// Вызов метода ToString переопределенного в WorkItem

Console.WriteLine(item.ToString());

// Вызов унаследованного метода Update для изменения названия рабочего элемента в экземпляре ChangeRequest

change.Update("Изменить базового класса дизайн",

new TimeSpan(4, 0, 0));

// ChangeRequest наследует переопределенный метод WorkItem'а - ToString.

Console.WriteLine(change.ToString());

// Задержка консоли для режима отладки

Console.WriteLine("Press any key to exit.");

Console.ReadKey();

}

}

/* Вывод:

1 - Исправить ошибки

2 - Изменить базового класса дизайн

*/

Полиформизм

О полиморфизме часто говорят как о третьем базовом элементе объектно-ориентированного программирования, после инкапсуляции и наследования. Полиформизм — это греческое слово, означающее "наличие многих форм". Это понятие имеет два различающихся аспекта:

  1. Во время выполнения объекты производного класса могут рассматриваться как объекты базового класса в таких местах как параметры метода и коллекции или массивы. При этом объявленный тип объекта больше не идентичен его типу времени выполнения.

  2. Базовые классы могут определять и реализовывать виртуальныеметоды, а производные классы могут переопределять их. Это означает, что они предоставляют свои собственные определения и реализацию. Во время выполнения, когда клиентский код вызывает метод, среда CLR ищет тип времени выполнения объекта и вызывает это переопределение виртуального метода. Таким образом, в исходном коде можно вызвать метод в базовом классе и вызвать выполнение метода с версией производного класса.

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

  1. Создание иерархии классов, в которой класс каждой конкретной формы производится от общего базового класса.

  2. Использование виртуального метода для вызова соответствующего метода в каком-либо производном классе одним вызовом метода базового класса.

Во-первых, создайте базовый класс, называемый Shape, и производные классы, например Rectangle, Circle и Triangle. Предоставьте классу Shape виртуальный метод, называемый Draw, и переопределите его в каждом производном классе для рисования конкретной формы, которую представляет класс. Создайте объект List<Shape> и добавьте в него круг, треугольник и прямоугольник. Для обновления поверхности рисования используйте цикл foreach для итерации списка и вызова метода Draw на каждом объекте Shape в списке. Хотя каждый объект в списке имеет объявленный тип Shape, будет вызываться именно тип времени выполнения (переопределенная версия метода в каждом производном классе):

public class Shape

{

// Несколько полей для примера

public int X { get; private set; }

public int Y { get; private set; }

public int Height { get; set; }

public int Width { get; set; }

// Виртуальный метод

public virtual void Draw()

{

Console.WriteLine("Базовый класс для выполнения задач рисования");

}

}

class Circle : Shape

{

public override void Draw()

{

// Код для рисования круга...

Console.WriteLine("Рисуется круг");

// Вызов версии метода Draw базового класса

base.Draw();

}

}

class Rectangle : Shape

{

public override void Draw()

{

// Код для рисования квадрата...

Console.WriteLine("Рисуется квадрат");

base.Draw();

}

}

class Triangle : Shape

{

public override void Draw()

{

// Код для рисования треугольника...

Console.WriteLine("Рисуется треугольник");

base.Draw();

}

}

class Program

{

static void Main(string[] args)

{

// Применение полиформизма #1: Rectangle, Triangle и Circle

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

// приведение к типу Shape не будет требоваться (потому что происходит неявное преобразование

// дочернего класса к базовому)

System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();

shapes.Add(new Rectangle());

shapes.Add(new Triangle());

shapes.Add(new Circle());

// Применение полиформизма #2: при вызове виртуального метода Draw

// вызывается именно тот метод, который был переопределен для дочернего класса

foreach (Shape s in shapes)

{

s.Draw();

}

// Задержка консоли для отладки

Console.WriteLine("Нажмите любую клавишу для выхода.");

Console.ReadKey();

}

}

/* Вывод:

Рисуется квадрат

Базовый класс для выполнения задач рисования

Рисуется треугольник

Базовый класс для выполнения задач рисования

Рисуется круг

Базовый класс для выполнения задач рисования

*/

Drag-n-Drop

Для понимания принципов работы с Drag-n-Dropлучше всего будет разобрать этот механизм на практике:

Для начала создайте проект и назовите его ZOO.

Схема классов

Работать с классами в большом проекте становится затруднительно. Для упрощения работы с классами в VisualStudioприсутствует механизм построения схемы классов описанных в проекте. Для того, чтобы запустить построение схемы классов вашего проекта, щёлкните правой кнопкой мыши по компоненту проекта в обозревателе решений и выберите там пункт меню «Перейти к схеме классов».

Например, результат построения схемы классов примера описанного в разделе полиформизм данной лабораторной работы:

Рис 7.3 Схема классов примера с полиформизмом

Элемент управления TreeView

Отображает иерархическую коллекцию помеченных элементов, каждый из которых представлен объектом TreeNode. На приведенной ниже иллюстрации показан пример элемента управления TreeView.

Рис 7.4 Пример с TreeView

Коллекция Nodes включает все объекты TreeNode, назначенные элементу управления TreeView. Узлы дерева в этой коллекции рассматриваются как корневые узлы дерева. Все узлы, впоследствии добавляемые к корневому узлу дерева, рассматриваются как дочерние узлы. Так как каждый объект TreeNode может содержать коллекцию других объектов TreeNode, могут возникнуть трудности при определении расположения в древовидной структуре в процессе итерации по коллекции. Может быть выполнен анализ строки TreeNode.FullPath с использованием значения строки PathSeparator, чтобы определить, где начинается и заканчивается метка узла TreeNode. Узлы дерева могут быть развернуты, чтобы отобразить следующий уровень дочерних узлов дерева. Пользователь может развернуть объект TreeNode путем нажатия кнопки "плюс" (+), если она отображается рядом с объектом TreeNode, или же можно развернуть объект TreeNode с помощью вызова метода TreeNode.Expand. Чтобы развернуть все дочерние уровни узлов дерева в коллекции Nodes, вызовите метод ExpandAll. Можно свернуть дочерний уровень узла TreeNode путем вызова метода TreeNode.Collapse, или же можно нажать кнопку со знаком "минус" (-) рядом с узлом TreeNode, если эта кнопка отображается на экране. Можно также вызвать метод TreeNode.Toggle, который позволяет осуществлять переход между развернутым и свернутым состояниями узла.

Вид элемента управления TreeView можно задать с помощью его свойств для установки стиля и отображения. При установке для свойства ShowPlusMinus значения true рядом с каждым узлом TreeNode отображается кнопка со знаком "плюс" или "минус", в зависимости от того, может ли этот узел быть развернут или свернут. Установка для свойства ShowRootLines значения true приводит к отображению в элементе управления TreeView линий, которые соединяют все корневые узлы дерева. Можно отобразить линии, соединяющие дочерние узлы дерева с их корневым узлом, с помощью установки для свойства ShowLines значения true. При установке для свойства HotTracking значения true изменяется вид меток узлов дерева при перемещении над ними указателя мыши. В этом случае при нахождении указателя мыши над узлом метка узла дерева принимает вид гиперссылки. Можно также полностью настроить вид элемента управления TreeView. Для этой цели задайте для свойства DrawMode значение, отличное от TreeViewDrawMode.Normal, и обработайте событие DrawNode.

Покажем на примере как можно использовать функциональность элемента TreeViewи элементов управления для представления иерархии объектов предметной области. Возьмем в качестве предметной области - животный мир. Саму иерархию можно представить так:

Рис 7.5 Иерархия животного мира

Конечным звеном цепочки будет считаться уже конкретное животное поля и методы которое оно унаследовало от родительских классов, то есть например если конкретное животное это Волк (экземпляр класса Пес), то поля характеризующие например состав молока, уже будут присутствовать так как они уже описаны были в классе Млекопитающие и были затем унаследованы классом Хищные, приобретая все новые и новые характеристики, т.е. поля, методы, свойства. Пусть наша задача состоит в составлении энциклопедии, где роль навигационного элемента будет играть элемент управления TreeView, с помощью кнопок можно будет настраивать тот или иной вид животных, добавлять новых или удалять старых, само окно будет выглядеть примерно так:

Здесь остальные унаследованные характеристики, свойственные типу хордовых, классу млекопитающих, отряду хищные, семейству псовых, роду собак, и новые характеристики именно для вида волков(средняя длина лап, кол-во зубов в пасти, вес и тд.)

Рис.7.6 Пример с TreeViewи наследованием

Сами корни, и узлы определяющие только класс, род, тип животного можно создать еще до начала выполнения приложения, с помощью конструктора. Вызвать конструктор дерева можно можно с помощью контекстного меню, т.е. щёлкнув правой кнопкой мыши по элементу TreeViewи выбрать пункт Настроить узлы… Таким образом, нам не надо создавать экземпляры классов Хордовые , млекопитающие и тд. так как они у нас всего лишь для того чтобы определить конечный тип, а например экземпляр класса Пес (или Псовый), нам уже необходимо будет создать, так как он будет хранить важную для нас информацию.

Чтобы правильно осуществить добавление необходимо делать проверку в обработчике события нажатия клавиши Добавить на то какой узел выбран, например если выбран узел Млекопитающие, то добавить новый вид туда нельзя, так как это только середина иерархии, для эффективной проверки, можно сделать проверку на выбранный уровень узлов с помощью свойства LevelклассаTreeNode. Примерный код:

if (treeView1.SelectedNode.Level == 4) // Нумерация уровней узлов идет с 0

{

// Добавление вида

}

Но внутри оператора ifпридется делать проверку на имя узла 4 уровня, чтобы определить к какому Роду относится то или иное животное, ведь именно род играет роль законченного типа, экземпляры которого должны создаваться.

Для добавления нового узла применяется метод treeView1.SelectedNode.Nodes.Add, т.е. получая выбранный TreeNodeс помощью свойстваSelectedNode, затем обращаемся к его коллекцииNodes, и вызываем методAdd(добавить) параметром которого является строка. Затем необходимо обратиться к добавленномуTreeNode, и связать его с реальным экземпляром который и будет хранить всю информацию и предоставлять ее в правой колонке, для просмотра и редактирования. Связать его можно с помощью свойстваTag, т.е. выглядит это примерно так:

AddedTreeNode.Tag = new Dog(…); // два в одном, создаём и связываем

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

Конечно в зависимости от предметной области функциональность и применение TreeViewи его возможностей может и должно меняться.

Сериализация

Допустим мы создали с помощью предыдущей программы небольшую базу данных, теперь нам необходимо ее сохранить. Сделать это можно разными способами, от ручного сохранения в файл, с учётом правильного считывания файла, пересоздания всех объектов, заполнения всех объектов и так далее, либо с помощью автоматизированных средств .NET, т.е. механизма сериализации. Оба этих методов хороши, и должны применяться по мере эффективности того или иного метода.

Сериализацией (serialization) называется процесс преобразования объекта или графа связанных объектов в поток байтов. Соответственно, обратное преобразование называется десериализацией (deserialization). Вот примеры применения этого удивительно полезного механизма:

  • Состояние приложения ( граф объекта) можно легко сохранить в файлена диске или в базе данных и восстановить при следующем запуск приложения. ASP.NET сохраняет и восстанавливает состояние сеанса путем сериализации и десериализации.

  • Набор объектов можно скопировать в буфер и вставить в то же или в другое приложение. Этот подход используется в приложениях Windows Forms и Windows Presentation Foundation (WPF).

  • Можно клонировать набор объектов и сохранить как резервную копию, пока пользователь работает с основным набором объектов.

  • Набор объектов можно легко передать по сети в процесс, запущенный на другой машине. Механизм удаленного взаимодействия платформы .NET Framework сериализует и десериализует объекты, продвигаемые по значению. Эта же технология используется при передаче объектов через границы домена.

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

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

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