Суббота, 27.04.2024, 01:47
Приветствую Вас Гость | RSS

Лекции

Меню сайта
Форма входа
Категории раздела
ТАУ (Теория автоматического управления) [31]
лекции по ТАУ
Экология [151]
учебник
Бухгалтерский учет и налогообложение в строительстве [56]
Дементьев А.Ю. - Практическое пособие
Психология [104]
Пип
информатика [80]
с# Карли Ватсон
современные стулья [0]
новинки
Поиск

Главная » 2010 » Февраль » 11 » Введение в объектно-ориентированное программирование
00:21
Введение в объектно-ориентированное программирование
Введение
в объектно-ориентированное
программирование
В предыдущих главах мы рассмотрели синтаксис языка С# и основы програм-
мирования на нем. Кроме того, мы научились собирать работоспособные консоль-
ные приложения. Однако для того, чтобы получить доступ ко всему потенциалу С#
и .NET Framework, необходимо научиться использовать методы объектно-ориен-
тированного программирования (ООП). На самом деле некоторые из этих методов
уже применялись нами, однако чтобы не усложнять жизнь, мы не акцентировали
на этом внимание.
В этой главе мы на некоторое время отвлечемся от кода и сосредоточимся на
принципах, лежащих в основе ООП. Такой подход очень скоро приведет нас об-
ратно к языку С#, поскольку последний неразрывно связан с ООП. Ко всем по-
нятиям, введенным в настоящей главе, мы будем возвращаться в дальнейшем,
иллюстрируя их с помощью примеров программ, так что не следует впадать в па-
нику, если не удается сразу понять материал.
Для начала рассмотрим базовые понятия ООП, которые и ответят на самый
фундаментальный вопрос: "Что такое объект?". Очень скоро вы увидите, что су-
ществует весьма большое количество терминов, связанных с ООП; поначалу это
может показаться достаточно сложным, хотя используемый язык будет подробно
разъяснять. Вы также убедитесь, что применение ООП требует нового взгляда на
программирование.
Помимо обсуждения общих принципов, лежащих в основе ООП, мы рассмот-
рим одну область, для которой исчерпывающее понимание ООП является необхо-
димым: приложения Windows Forms. Этот тип приложений (т. е. приложения,
в которых используется среда Windows и такие возможности этой среды, как меню,
кнопки и т. п.) оставляет широкий простор для описания объектов, и мы постара-
емся эффективно проиллюстрировать принципы ООП в среде Windows Forms.
Обратите внимание, что ООП в том виде, в котором
оно представлено в настоящей главе, на самом деле является
ООП для .NET, и поэтому некоторые из способов, обсуждаемых
здесь, неприменимы к другим разновидностям среды ООП.
Такой подход при изучении С# является вполне оправданным,
поскольку впоследствии это существенно упростит вам жизнь.
Итак, давайте начнем с самого начала.
Введение в объектно-ориентированное программирование 161
Что такое объектно-ориентированное
программирование
Объектно-ориентированное программирование — это относительно новый под-
ход к созданию компьютерных приложений, в котором предпринимается попытка
решить многие проблемы, возникающие при применении "традиционного" про-
граммирования. Тип программирования, с которым вы были до сих пор знакомы,
известен под названием функционального (или процедурного) программирования.
Он очень часто приводит к созданию так называемых монолитных приложений,
в которых все функциональные возможности содержатся в небольшом числе моду-
лей (зачастую в одном). Применяя методы ООП, мы очень часто будем использо-
вать большое количество программных модулей, каждый из которых предоставляет
только одну функциональную возможность, причем каждый модуль может суще-
ствовать отдельно от остальных и даже быть совершенно независимым. Такой
модульный метод написания программ оказывается более гибким и увеличивает
возможности для повторного использования кода.
В качестве более наглядной иллюстрации ООП представим себе сложное при-
ложение в виде высококлассного гоночного автомобиля. Если бы использовались
традиционные способы программирования, то этот автомобиль представлял бы
собой единое целое. И пожелай мы его усовершенствовать, нам бы пришлось за-
менить его целиком, отправив на завод-изготовитель для доводки заводскими спе-
циалистами (или купить новый). Если же применяются методы ООП, то можно
просто приобрести новый двигатель и, следуя прилагающимся к нему инструкци-
ям, заменить его самостоятельно.
В "традиционном" приложении порядок выполнения прост и прямолинеен.
Приложения загружаются в память, их выполнение начинается в точке А и завер-
шается в точке В, после чего они выгружаются из памяти. В процессе выполнения
такого приложения могут использоваться самые разнообразные объекты, напри-
мер, файлы на различных носителях или возможности, предоставляемые видеокар-
той, однако основное тело вычислений расположено в едином месте. Весь код, как
правило, предназначается для манипуляций над данными с помощью различных
математических и логических средств. Способы такого манипулирования обычно
очень просты благодаря применению базовых типов (таких как целый и логиче-
ский) ДДЯ более сложного представления данных.
При использовании ООП такая прямолинейность встречается редко. И хотя
результат достигается тот же самый, способ его получения зачастую оказывается
совершенно отличным. Методы ООП в большей степени основываются на струк-
туре и значении данных, а также на взаимодействии между различными данными.
Это обычно требует больших усилий при разработке проекта, но дает преимущест-
во расширяемости. После того как соглашение о способе представления конкрет-
ного типа данных достигнуто, оно может использоваться и в более поздних версиях
приложения, и в совершенно новых приложениях. Тот факт, что такое соглашение
уже существует, позволяет радикально сократить время, затрачиваемое на разра-
ботку. Это позволяет объяснить вышеприведенный пример с гоночным автомоби-
лем. В этом случае в качестве соглашения применяется такая структура кода для
"двигателя", которая позволяет использовать новый код (новый двигатель) взамен
старого без всяких проблем и не требует отправки автомобиля на завод.
162 Глава 8
Кроме соглашений о представлении данных, ООП часто позволяет упростить
жизнь за счет соглашений о структуре и об использовании более абстрактных
сущностей. Например, соглашение может быть достигнуто не только относительно
формата данных, который должен применяться при отправке выходного потока на
какое-либо устройство вроде принтера, но и относительно способов обмена данны-
ми с этим устройством. Это будет касаться инструкций, которые оно может вос-
принимать и в дальнейшей работе.
Как можно предположить по названию, все это достигается за счет использова-
ния некоторых объектов. Так что же собой представляет объект?
Что такое объект
Объект — это строительный блок ООП-приложения. В этом блоке инкапсули-
руется некоторая часть приложения, которая может представлять собой процесс,
группу данных или какую-либо более сложную сущность.
В простейшем смысле объект очень напоминает тип структуры, с которым вы
познакомились раньше и в котором содержатся члены в виде переменных и функ-
ций различных типов. Совокупность переменных представляет данные, хранящиеся
в этом объекте, а функции обеспечивают доступ к функциональным возможностям
объекта. Несколько более сложные объекты могут вообще не содержать никаких
данных; вместо это они могут представлять некоторый процесс и состоять исклю-
чительно из функций. Например, можно использовать объект, представляющий
принтер, которой состоит из функций, обеспечивающих контроль за работой этого
устройства (распечатать документ, распечатать тестовую страницу и т. д.).
Объекты в С# создаются на основе типов точно так же, как и переменные. Тип
объекта известен в ООП под специальным названием "класс объекта". Мы мо-
жем использовать определения классов для создания экземпляров объектов, что
означает создание реального поименованного экземпляра данного класса. Слово-
сочетание "экземпляр данного класса" и термин "объект" в данном случае означа-
ют одно и то же, однако уверяем вас, что, вообще говоря, "класс" и "объект"
обозначают фундаментально отличающиеся понятия.
В данной главе мы будем описывать классы и объекты с использованием син-
таксиса Unified Modeling Language (UML — унифицированный язык моделиро-
вания). UML — это язык, разработанный для моделирования приложений с ис-
пользованием тех объектов, из которых они строятся, тех операций, которые они
выполняют, и тех вариантов их применения, для которых они предназначаются.
В данном случае мы будем использовать только основы этого языка, объясняя тре-
бующиеся нам понятия по ходу дела и не отвлекаясь на более сложные аспекты.
Диаграммы, использованные в данной главе, создавались
с помощью программы Microsoft Visio, которая поставляется
с коммерческой версией VS.
Printer
mvPrinter: Printer
На рисунке справа приводится UML-представление класса, пред-
назначенного для описания принтера, названного Printer. Имя класса
изображено в верхней секции этого квадрата (к двум его нижним сек-
циям мы обратимся немного позднее).
На рисунке слева изображено UML-представление экземпляра класса Printer
с именем myPrinter. Здесь в верхней секции располагается имя экземпляра, за
ним следует имя соответствующего класса. Имена разделяются двоеточием.
Введение в объектно-ориентированное программирование 163
Свойства и поля
Свойства и поля обеспечивают доступ к данным, хранящимся в объекте. Дан-
ные, содержащиеся в объектах,— это то, что отличает один объект от другого, по-
скольку в различных объектах одного и того же класса могут храниться различные
значения свойств и полей.
Теперь нам потребуется ввести еще один термин. Различные части информа-
ции, хранящиеся в объекте, в совокупности определяют состояние объекта.
Представьте себе класс объектов с именем CupOfCoffee (чашка кофе), описы-
вающий чашку кофе. При создании экземпляра этого класса (т. е. при создании
объекта данного класса) мы должны описать его состояние, для того чтобы объект
был осмысленным. В этом случае мы могли бы использовать такие свойства и поля,
которые позволят программе, использующей этот объект, задавать марку кофе,
определять, добавлены ли в кофе сахар и молоко, является ли этот кофе раствори-
мым и т. д. В таком случае объект, представляющий чашку кофе, будет обладать
определенным состоянием, например, "колумбийский фильтрованный кофе с моло-
ком и двумя кусочками сахара".
Как поля, так и свойства являются типизированными, поэтому информацию,
содержащуюся в них, можно хранить в переменных типа string, int и т. п. Однако
свойства отличаются от полей тем, что они не обеспечивают непосредственного
доступа к данным. Объекты обладают возможностью изолировать пользователей,
которым не требуется точное представление о структуре существующих свойств,
от реального устройства своих данных. Если для описания числа кусочков сахара
в экземпляре CupOfCoffee мы используем поле, то пользователи смогут занести
в это поле любое значение, какое им заблагорассудится; если же мы используем
свойство, то мы сможем ограничить это значение диапазоном от 0 до 2.
Вообще говоря, для организации доступа к состоянию лучше использовать
свойства, а не поля, поскольку в этом случае мы обладаем большим контролем за
происходящим, хотя синтаксис и в том, и в другом случае применяется один и тот
же. Режим доступа к свойствам также может быть четко определен для данного
объекта. Некоторые свойства могут использоваться в режиме "только чтение", что
дает возможность просматривать их, но не изменять (по крайней мере, непосред-
ственно). Очень часто полезными оказываются способы, позволяющие одновре-
менно считывать несколько различных частей информации. Например, мы можем
описать свойство класса cupofcoffее с именем Description (описание), использу-
ющееся в режиме "только чтение", которое при обращении будет возвращать
строку, описывающую состояние некоторого экземпляра данного класса (напри-
мер, строку, приведенную выше). Эти же самые данные можно было бы получить,
обращаясь к нескольким различным свойствам, однако использование одного
свойства позволяет экономить время и усилия. Аналогичным образом можно ис-
пользовать свойства, работающие в режиме "только запись".
Подобно тому как мы имеем возможность установить режим чтения/записи
свойств, мы можем задать тип доступа совместно для полей и свойств. Это по-
зволяет определять, какие программы имеют доступ к этим членам: доступны ли
они для всего кода (public — общие), только для кода в рамках данного класса
(private — частные), или же доступ к ним определяется по более сложной схеме
(этот вопрос будет рассматриваться в настоящей главе по мере необходимости).
Чрезвычайно распространенной является практика, когда поля описываются как
частные, а доступ к ним организовывается посредством свойств, описанных как
общие. Это означает, что код в рамках данного класса обладает непосредственным
доступом к данным, хранящимся в поле, а общее свойство скрывает эти данные от
164 Глава 8
CupOfCoffee
+BeanType: string
•«-Instant: bool
+Milk: bool
+Sugar: byte
-(•Description: string
внешних пользователей и не позволяет им записывать в эти данные недопустимые
значения. Принято говорить, что общие члены предоставляются данным классом.
Для большей наглядности давайте отождествим это с областью действия пере-
менной. Частные поля и свойства в таком случае можно представить как локаль-
ные по отношению к объекту, которому они принадлежат, в то время как область
действия общих полей и свойств также распространяется и на код, внешний по
отношению к данному объекту.
При представлении класса посредством UML вторая секция используется для
изображения свойств и полей.
На рисунке слева можно видеть представление нашего класса cupofcoffee,
в котором описано пять членов (неважно, свойств или полей — в UML между
ними не существует никаких отличий), обсуждавшихся нами ранее. Каждое
вхождение содержит следующую информацию:
О Тип доступа: символ 4- используется для общего члена,
символ - используется для частного члена. В дальнейшем мы не будем
изображать частные члены на диаграммах данной главы, поскольку
эта информация является внутренней по отношению к классу.
Не будет приводится также и информация, касающаяся режима доступа
(чтение/запись).
• Имя члена,
а Тип члена.
В качестве разделителя между именем и типом члена используется двоеточие.
Методы
Метод — это термин, используемый для обозначения функций, предоставляе-
мых данным объектом. Методы могут вызываться точно так же, как и любые дру-
гие функции, и в них так же, как и в функциях, могут использоваться возвращаемые
значения и параметры (более подробно эта тема описывается в главе 6).
Методы применяются для обеспечения доступа к функциональным возможно-
стям объектов. Подобно полям и свойствам, они могут быть общими или частны-
ми, ограничивая по мере необходимости доступ к ним из внешнего кода. Методы
часто учитывают состояние объекта при своей работе и обладают доступом к част-
ным членам, например, к частным полям. Так, в классе CupOfCoffee мы можем
описать метод с именем AddSugarO (добавить сахар), который обеспечит исполь-
зование более удобного синтаксиса для увеличения значения свойства сладости
кофе, чем присвоение соответствующего значения свойству sugar (сахар).
В UML при описании объектов для изображения
функций используется третья секция прямоугольни-
ка (см. рис. справа).
Используемый в этом случае синтаксис аналоги-
чен применяемому для полей и свойств, за исклю-
чением того, что в конце строки указывается тип
возвращаемого значения, а также приводятся пара-
метры. В UML каждый параметр изображается с одним из следующих идентифи-
каторов: in, out или inout. Эти идентификаторы используются для обозначения
направления потока данных, при этом out и inout в первом приближении соответст-
вуют применяемым в С# ключевым словам out и ref, описанным в главе 6. Иден-
тификатор in примерно соответствует такому поведению С#, когда отсутствуют оба
ключевых слова.
CupOfCoffee
+BeanType : string
+lnstant: bool
+Milk: bool
+Sugar: byte
+Description : string
+AddSugar(in amount: byte): byte
Введение в объектно-ориентированное программирование 165
Объектом может быть все
Настал момент, когда необходимо, наконец, объясниться. Мы использовали
объекты, свойства и методы на всем протяжении этой книги. В действительности
в С# и .NET Framework объектом может быть все, что угодно. Функция Main о
в консольном приложении является методом класса. Все типы переменных, рас-
смотренных нами, являются классами. Каждая из использовавшихся команд пред-
ставляет СОбоЙ СВОЙСТВО ИЛИ метод, Например, <Строка>.Length, <Стрсжа>. ToUpperO
и т. д. В данном случае точка отделяет имя экземпляра объекта от свойства или от
имени метода.
Объекты существуют буквально повсеместно, и синтаксис, который требуется
для их использования, зачастую оказывается очень простым. Действительно, он
настолько прост, что до настоящего времени позволял нам сосредоточиться на
более фундаментальных аспектах С#.
С этого момента мы приступаем к более детальному рассмотрению объектов.
Имейте в виду, что понятия, которые будут вводиться, обладают весьма широкой
областью использования. Они применимы даже к той простой маленькой перемен-
ной типа int, с которой вы так любили играться. К счастью, мы можем с успехом
использовать их и для более сложных понятий.
Жизненный цикл объекта
У каждого объекта имеется четко определенный жизненный цикл, который
длится с момента использования определения класса до уничтожения объекта.
Кроме обычного состояния "используется", в жизненном цикле объекта присутст-
вуют еще два важных этапа:
• Создание объекта — состояние, когда происходит
первоначальное создание экземпляра объекта. Такая инициализация
известна под названием создания объекта и осуществляется
конструктором объекта.
• Уничтожение объекта — состояние, когда происходит
уничтожение объекта, при котором очень часто возникает
необходимость проведения различных восстановительных мероприятий,
например освобождения памяти. Эта работа входит в функции
деструктора объекта.
Конструкторы
Инициализация объекта происходит автоматически. Нам не приходится беспо-
коиться, допустим, о поиске свободной памяти, в которой будет размещен новый
объект. Однако иногда возникают ситуации, когда на этапе инициализации может
потребоваться выполнение каких-либо дополнительных действий. Например, очень
часто бывает необходимо инициализировать данные, хранящиеся в объекте. Как
раз это и входит в функции конструктора.
У каждого объекта имеется конструктор, используемый по умолчанию, кото-
рый представляет собой метод без параметров. Его имя совпадает с именем самого
класса. Определение класса может включать в себя несколько конструкторов. Они
могут иметь отличающиеся сигнатуры, которые используются для создания экзем-
пляра объекта. Для присваивания начальных значений данным, хранящимся внут-
ри объекта, часто применяются конструкторы с параметрами.
166 Глава 8
В С# конструктор можно вызвать, введя ключевое слово new. Например, мы
можем создать объект типа string следующим образом:
string myString = new string () ;
Объекты также могут создаваться с помощью конструктора, используемого не
по умолчанию. Как и имя конструктора по умолчанию, имена этих конструкторов
совпадают с именем класса, но у них имеются еще и параметры. Используются
они аналогичным образом:
string myString = new string{ia/
/ 10);
В результате применения этого конструктора будет создана новая строка, которой
В Качестве НачаЛЬНОГО Значения будет присвоено "аааааааааа", Т. е. СИМВОЛ "а",
повторенный десятикратно.
Конструкторы, подобно полям, свойствам и методам, могут быть общими или
частными. Код, внешний по отношению к данному классу, не может использовать
для создания экземпляра объекта частный конструктор; для этого необходимо вос-
пользоваться общим конструктором. Таким способом мы можем, например, заста-
вить пользователей нашего класса применять конструктор, использующийся не по
умолчанию.
В некоторых классах вообще отсутствуют общедоступные конструкторы, что
означает невозможность для внешнего кода создавать экземпляры данного класса.
Однако, как вы вскоре сможете убедиться, такие классы вовсе не являются совер-
шенно бесполезными.
Деструкторы
Деструкторы используются в .NET Framework для того, чтобы выполнять убор-
ку за объектами. В общем случае нам не требуется писать какой бы то ни было
код для метода деструктора; напротив, за нас будет работать операция, выполняю-
щаяся по умолчанию. Однако если необходимо выполнить какие-либо важные дей-
ствия перед уничтожением экземпляра объекта, то можно задать определенные
инструкции.
Очень важно запомнить, что метод деструктора объекта не вызывается сразу,
как только данный объект перестает использоваться.
Когда, например, мы выходим за область действия переменной, она становится
недоступной для нашего кода, но при этом она может по-прежнему существовать
где-нибудь в памяти компьютера. Только после того как сборщик мусора среды
исполнения .NET выполнит свою работу, она будет окончательно уничтожена.
Это означает, что не следует полагаться на деструктор
в плане освобождения ресурсов, которые использовались
экземпляром объекта, поскольку объект может оставаться
неиспользуемым на протяжении длительного времени.
Если используемые ресурсы критичны, то это может привести
к возникновению проблем. Однако здесь имеется решение,
и оно рассматривается в разделе "Удаляемые объекты"
этой главы.
Введение в объектно-ориентированное программирование 167
Статические члены класса и члены класса экземпляра
Наряду с такими членами, как свойства, методы и поля, относящимися к конк-
ретным экземплярам объектов, существует возможность использовать статиче-
ские (static) члены (известные также под названием членов с общим доступом
(shared)), среди которых могут быть методы, свойства и поля. Доступ к статиче-
ским членам имеют все экземпляры данного класса, поэтому их можно предста-
вить себе в качестве глобальных для всех объектов данного класса. Статические
свойства и поля позволяют осуществлять доступ к данным независимо от экземп-
ляров объектов, а статические методы позволяют выполнять команды, которые
относятся к данному типу объектов, но не к конкретным экземплярам объектов.
Для того чтобы использовать статические члены нам, по существу, даже не требу-
ется создавать экземпляры объектов с типом данного класса.
Использовавшиеся нами методы Console.WriteLine () И Convert .ToString ()
являются статическими. Ни в какой момент времени от нас не требуется создавать
экземпляры классов console или convert (более того, даже если бы мы и попыта-
лись это сделать, то все равно ничего бы не получилось, поскольку к конструкто-
рам этих классов не существует общего доступа, что обсуждалось ранее).
Существуют различные ситуации, при которых статиче-
ские свойства и методы могут быть использованы с боль-
шой пользой.
В синтаксисе UML статические члены классов выделя-
ются подчеркиванием (см. рис. справа).
MyClass
+lnstanceProperty : int
+StaticPropertv: int
+lnstanceMethod(): void
+StaticMethod(): void
Методы ООП
Теперь, когда вы познакомились с основами и узнали, что представляют собой
объекты и как они работают, мы потратим определенное время на изучение неко-
торых других возможностей объектов. В этом разделе будут рассматриваться:
• Интерфейсы
• Наследование
• Полиморфизм
• Взаимоотношения между объектами
• Перегрузка операторов
• События
Интерфейсы
Интерфейсом называется семейство явно описанных как public методов
и свойств, которые сгруппированы в единое целое и инкапсулируют какую-либо
определенную функциональную возможность. После того как интерфейс опреде-
лен, его можно реализовать в некотором классе. Это означает, что класс будет
поддерживать все свойства и члены, определяемые данным интерфейсом.
Обратите внимание, что интерфейсы не могут существовать сами по себе. Мы
не можем "создать экземпляр интерфейса" таким же образом, как мы создаем эк-
земпляр класса. Кроме того, интерфейс не может содержать в себе никакого кода,
который бы реализовал его члены; он просто описывает эти члены. Их реализация
должна находиться в классах, в которых реализован данный интерфейс.
168 Глава 8
«Interface»
IHotDrink
•Instant: bool
+Milk: bool
+Sugar: byte
^Description: string
+AddSugar(in amount: byte): byte
CupOfCoffee
+BeanType: string
CupOfTea
+LeafType: string
В предыдущем примере, касающемся кофе, мы могли бы сгруппировать в ин-
терфейс несколько свойств и методов наиболее общего назначения, например,
AddSugarO, Milk (МОЛОКО), Sugar (сахар) И Instant (растворимый). Такой ИНТер-
фейс можно назвать как-нибудь вроде IHotDrink (горячий напиток) (имена интер-
фейсов обычно предваряются заглавной буквой i). Этот интерфейс допустимо
использовать и для каких-нибудь других объектов, возможно, принадлежащих
к классу CupOfTea (чашка чая). Другими словами, мы получаем возможность обра-
щаться с этими объектами одинаковым образом, а они при этом по-прежнему
могут обладать своими собственными индивидуальными свойствами (например,
ВеапТуре (сорт КофеЙНЫХ бобов) ДЛЯ CupOfCoffee И LeafType (сорт ЧаЙНОГО ЛИСТа)
ДЛЯ CupOfTea).
Интерфейсы, реализованные на
О IHotDrink объектах UML, изображаются с по-
мощью "леденцового" (lollipop) син-
таксиса. На диаграмме, приведенной
на рисунке Слева, ЧЛеНЫ IHotDrink
выделены в отдельный прямоуголь-
О IHotDrink ник с помощью синтаксиса, напоми-
нающего используемый для описания
классов (к сожалению, существую-
щая версия Visio не позволяет интер-
фейсам иметь поля или свойства).
У класса может быть несколько интерфейсов, и несколько классов могут под-
держивать один и тот же интерфейс. Понятие интерфейса позволяет, таким обра-
зом, облегчить жизнь другим разработчикам и пользователям. Допустим, что у вас
имеется некий код, в котором задействован объект, обладающий определенным
интерфейсом. Если предположить, что вы не используете другие свойства и мето-
ды данного объекта, то появляется возможность заменять один объект на другой
(код, в котором используется интерфейс ihotDrink, может работать как с экземп-
ляром класса CupOfCof fee, так и с экземпляром класса CupOfTea). Кроме того, раз-
работчик самого объекта имеет возможность снабжать вас усовершенствованными
версиями этого объекта — если он поддерживает использовавшийся ранее интер-
фейс, то не составит никакого труда применить новую версию объекта в вашей
программе.
Удаляемые объекты
Одним из интерфейсов, представляющих особенный интерес, является интер-
фейс iDisposabie. На объекте, который поддерживает интерфейс iDisposabie,
должен быть реализован метод DisposeO, т. е. в нем должен содержаться код для
реализации этого метода. Этот метод может вызываться, когда объект больше не
нужен (например, перед выходом из области его действия); он освобождает некие
критические ресурсы, которые в противном случае будут удерживаться до тех пор,
пока сборкой мусора не будет запущен метод деструктора. Это дает дополнитель-
ные возможности для управления ресурсами, которые используются объектами.
В С# имеется возможность применять специальную конструкцию, которая за-
мечательно использует этот метод. Ключевое слово using позволяет нам инициа-
лизировать некий объект, использующий критические ресурсы, внутри некоторого
блока кода, в конце которого автоматически вызывается метод DisposeO. Вот ка-
ким образом можно реализовать такую конструкцию:
Введение в объектно-ориентированное программирование 169
using (<ИмяКласса> <ИмяПеременной> = new <ИмяКласса>{) )
§11;:;;:;:;. :•:;,,,,;:.,
В этой конструкции переменную <имяпеременной> можно использовать по всему
блоку, но в конце блока она будет автоматически удалена (посредством вызова
метода Dispose О ).
Наследование
Наследование является одной из наиболее важных особенностей ООП. Любой
класс может наследоваться от другого класса, что означает, что он будет обладать
всеми членами того класса, от которого он наследуется. В терминологии ООП
класс, от которого происходит наследование (или от которого происходит наследу-
ющий класс), называется классом-родителем (иначе известным под названием
базового класса). В С# все объекты происходят от единственного базового класса.
Наследование позволяет расширять или создавать более специфические классы
на основе одного базового класса. Например, представьте себе класс, описываю-
щий скотный двор (такой класс использовался асом-разработчиком восьмидесяти-
летним Стариной Макдональдом в его приложении для работы с домашними
животными). Этот класс можно назвать Animal (животное) и включить в него та-
кие методы, как EatFoodO (принимать пишу) и Breed о (размножаться). После
этого мы можем создать производный класс с именем Cow (корова), который будет
поддерживать все эти методы, но при этом может обладать и своими собственны-
ми, например, моо() (мычать) и suppiyMiikO (давать молоко). Мы можем созда-
вать и другие производные классы, например, класс chicken (курица) с методами
cluck о (кудахтать) и LayEggO (нести яйца).
В UML наследование изображается с помощью
стрелок (см. рис. справа)*
Здесь для краткости опущены члены,
обладающие возвращаемыми
значениями.
При наследовании от одного базового класса
возникает очень важный вопрос о режиме досту-
па к членам данного класса. Частные члены базо-
вого класса, в отличие от общих членов, доступны
не будут. Однако общие члены будут доступны как производному классу, так
и внешнему коду. Это означает, что при использовании только этих двух режимов
доступа мы не можем получить член, который был бы доступен базовому классу
и производному классу, но не был бы при этом доступен для внешнего кода.
Для решения этой проблемы вводится третий режим доступа — protected
(защищенный), при котором доступ открыт только для производных классов.
С точки зрения внешнего кода этот режим доступа идентичен режиму доступа
к частному члену — ни к одному, ни к другому доступа не имеется.
Кроме степени защиты данного члена мы можем определять его поведение при
наследовании. Члены базового класса могут описываться как virtual (виртуальный),
что означает, что такой член может быть переопределен классом, который его
наследует. Другими словами, производный класс может использовать альтерна-
тивную реализацию данного члена. Такая альтернативная реализация не отменяет
Animal
+EatFood()
+Breed()
Г
Chicken
++LCaulycEkg()g()
1
Cow
++MSuopop()ylMkli()
170 Глава 8
Chciгken
Animal
++BEaretFeodo()d()
t
++LCaulycEkg()g()
Cow
++MSuopop)(ylMkli() +EatFood()
оригинальный код, который по-прежнему остается доступным в рамках этого
класса, однако он становится скрытым для внешнего кода. Если же никакой аль-
тернативной реализации не используется, то внешний код обладает доступом к ре-
ализации данного члена в базовом классе.
Заметьте, что виртуальные члены не могут быть частными, поскольку это при-
вело бы к парадоксу: нельзя одновременно утверждать, что член может быть пере-
определен производным классом и в то же время быть для него недоступным.
В примере с животными мы можем объявить метод EatFoodO
виртуальным и использовать для него другую реализацию во всех
производных классах, например, в классе cow (см. рис. слева).
Здесь метод EatFoodO изображен как для класса Animal, так
и для класса cow с той целью, чтобы показать, что в каждом классе
используется своя собственная реализация.
Базовые классы могут описываться как abstract (абстрактные).
Абстрактный класс не допускает непосредственного создания экзем-
пляра. Для того чтобы можно было им воспользоваться, он должен
быть унаследован другим классом. У абстрактных классов могут
иметься абстрактные члены, не имеющие никакой реализации, т. е.
их реализация должна присутствовать в производном классе.
Если допустить, что класс Animal — абстрактный,
то в UML он должен изображаться так, как показа-
но на рисунке справа.
Имена абстрактных классов выводятся курсивом
(или иногда прямоугольник, в котором они находят-
ся, ограничивается пунктирной линией).
Наконец, класс может быть описан как sealed
(изолированный). Изолированный класс нельзя ис-
пользовать в качестве базового класса, поэтому
у него не может быть производных классов.
В С# существует общий базовый класс для всех
объектов, называемый object (этот же класс в .NET
Framework называется system.object). В следующей
главе мы познакомимся с ним поближе.
Интерфейсы, описанные в этой главе, также могут наследоваться
от других интерфейсов. Однако в отличие от классов интерфейсы
могут наследоваться от нескольких базовых интерфейсов
(таким же образом, как классы могут поддерживать
по несколько интерфейсов).
Полиморфизм
Одно из следствий использования иерархии наследования заключается в том,
что методы и свойства, предоставляемые производными классами, перекрываются.
По этой причине часто существует возможность применять объекты, являющиеся
экземплярами классов, в качестве объектов базового типа и использовать для них
общий идентичный синтаксис. Например, если базовый класс с именем Animal об-
ладает методом EatFoodO, то синтаксис вызова этого метода из производных клас-
сов Cow и chicken будет одинаковым:
Cow myCow = new Cow {) ;
Chicken myChicken = new Chicken О ;
myCow.EatFood();
••; г:: Г :: m y C h i c k e n . E a t F o o d O ; ' ; • • : :; • .- .••;x:f: :.'.-\.,: ."••••••':•.••:..•;• '..:•:•• ,::'•. : .: .'"• : .: ; - ^ \ Л
A n i m a l
+ E a t F o o d ( )
+ B r e e d ( )
1
C h i c k e n
+ C l u c k ( )
+ L a y E g g ( )
+ E a t F o o d ( )
+ B r e e d ( )
C o w
+ M o o ( )
+ S u p p l y M i l k ( )
+ E a t F o o d ( )
+ B r e e d ( )
Введение в объектно-ориентированное программирование . 171
Полиморфизм позволяет пойти еще дальше. Существует возможность присво-
ить переменной базового типа переменную, которая имеет один из производных
типов. Например:
Animal my Animal = myCow;
При этом не требуется никакого приведения типов. С помощью этой переменной
мы получаем возможность обращаться к методам базового класса:
myAnimal.EatFood();
Это приведет к вызову метода EatFoodO, реализованного в производном классе.
Обратите внимание, что не существует возможности вызывать аналогичным спо-
собом методы, определенные в производном классе. Следующий код работать не
будет:
m y A n i m a l . M o o ( ) ;
Однако мы можем привести тип переменной базового типа к типу производного
класса и после этого вызвать метод производного класса:
Cow myNewCow =••• (Cow)myAnimal;
• x ; . .:-\ ,"./. myNewCow.Moo() ; • . •'••, / у ^'-•••'•:'i'"•• ' • •/ •
Такое приведение типа послужит причиной возникновения исключительной си-
туации в том случае, если исходная переменная не будет относиться к классу cow
или классу, производному от класса cow. Существуют способы указать, к какому
типу принадлежит объект, но этот вопрос будет рассмотрен в следующей главе.
Полиморфизм оказывается чрезвычайно полезным методом, позволяя выпол-
нять операции над различными объектами, происходящими от одного класса, с ис-
пользованием минимального количества кода.
Обратите внимание, что использовать полиморфизм могут не только классы,
у которых имеется один и тот же непосредственный класс-родитель: существует
возможность таким же образом обращаться, например, к дочернему и внучатому
классам, поскольку у них имеется общий класс в иерархии наследования.
И еще одно замечание. Запомните, что в С# все классы являются производными
от базового класса object, который является корневым в их иерархии наследова-
ния. Следовательно, с любыми объектами можно обращаться как с экземплярами
класса object. Именно поэтому функция console.writeLineо оказывается в со-
стоянии обрабатывать бесконечное количество комбинаций параметров при фор-
мировании строки. Каждый параметр, кроме первого, рассматривается как экземпляр
класса object, в результате чего возможен вывод на экран выходного потока про-
извольного объекта. Для этого вызывается метод Tostringo (член класса object).
Мы можем переопределить этот метод посредством некоторой реализации, под-
ходящей для нашего класса, либо просто использовать вариант по умолчанию,
который возвращает имя класса (квалифицированное в соответствии с тем про-
странством имен, в котором оно находится).
Полиморфизм интерфейсов
Ранее мы уже ввели понятие интерфейса, который позволяет сгруппировать
взаимосвязанные методы и свойства. Хотя создавать экземпляры интерфейсов по-
добно тому, как создаются экземпляры объектов, нельзя, мы можем использовать
переменные интерфейсного типа. В этом случае появляется возможность доступа
к методам и свойствам, предоставляемым этим интерфейсом, на объектах, которые
его поддерживают.
172 Глава 8
Предположим, что вместо т
Категория: информатика | Просмотров: 1451 | Добавил: basic | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Календарь
«  Февраль 2010  »
ПнВтСрЧтПтСбВс
1234567
891011121314
15161718192021
22232425262728
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

krutoto.ucoz.ru
Бесплатный конструктор сайтов - uCoz