CFA LogoCFA Logo Computer
Новости Статьи Магазин Драйвера Контакты
Новости
RSS канал новостей
В конце марта компания ASRock анонсировала фирменную линейку графических ускорителей Phantom Gaming. ...
Компания Huawei продолжает заниматься расширением фирменной линейки смартфонов Y Series. Очередное ...
Компания Antec в своем очередном пресс-релизе анонсировала поставки фирменной серии блоков питания ...
Компания Thermalright отчиталась о готовности нового высокопроизводительного процессорного кулера ...
Компания Biostar сообщает в официальном пресс-релизе о готовности флагманской материнской платы ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

АРХИВ СТАТЕЙ ЖУРНАЛА «МОЙ КОМПЬЮТЕР» ЗА 2003 ГОД

ООП-ля!

(c)Петр 'Roxton' СЕМИЛЕТОВ

Концепция объектно-ориентированного программирования (ООП) дает программисту невероятно удобные способы выражать свои алгоритмические мысли просто и эффективно. Между тем, часто даже программисты-профи не используют ООП в своих разработках, предпочитая реализовать код через устаревшую парадигму процедурного программирования. Отличный пример тому — знаменитый текстовый редактор Bred. Однако, профи на то и профи.
Но. Есть много «но» против процедурной парадигмы. Многие вещи, которые столь кратко и логично выражаются в рамках ООП, при использовании одних лишь процедур превращают код в неудобоваримую кашу. А хорошо прописанные ООП-классы не нуждаются даже в комментариях, настолько все очевидно. Бывают случаи, когда без ООП обойтись можно, но хлопотно. Об одном из таких случаев, весьма практическом, я расскажу в этой статье, чтобы на его примере объяснить, что же такое ООП и как оно работает на деле.

Для примеров в качестве языка программирования возьмем Object Pascal (Delphi). Вероятно, примеры сработают и во FreePascal, но я не проверял. Хотя C++ предоставляет куда большую функциональность в плане ООП, эта статья предназначена для познавательно-учебных целей, а Object Pascal заслуженно пользуется славой языка, код которого читается как обычный текст, а не хитромудрые формулы. Да и не секрет, что большинство отечественных программеров пользуются в работе именно Delphi.

Итак, случай из жизни. Сейчас я пишу программу — звонилку к провайдеру. Подобные звонилки обычно не что иное, как надстройки над RAS — системным сервисом, предоставляющим функции удаленного доступа. Существует RAS API — набор функций, с помощью которых программа может общаться с этим сервисом. Например, создавать и удалять аккаунты, звонить и так далее. Разумеется, я хотел бы из своей программы получать статистику по текущему соединению с провайдером, а именно — скорость соединения и количество переданных и полученных байт.

Казалось бы, все замечательно. В RAS API есть функция RasGetConnectionStatistics, которая дает нужную мне информацию. Однако беда в том, что эта функция поддерживается только в Windows NT/2000/XP, а чтобы получить аналогичную информацию в Windows 95/98/Me, надо читать данные из особого раздела реестра. Две задачи, возвращающие один результат, но реализованные по-разному.

Что бы сделал «процедурный» программист? Он бы взял и написал одну здоровенную функцию, в которой проверял бы, какая у пользователя система, и в зависимости от этого вызывал RasGetConnectionStatistics или читал реестр. Но, простите, проверка версии системы тоже отнимает процессорное время. Представьте, что такая проверка вызывается с периодичностью, скажем, раз в секунду. Да, я очень скуп на ресурсы. Как Скрудж МакДак. С помощью ООП мы решим задачу более оптимально, чем это сделал бы «процедурщик».

Я не буду касаться физической реализации получения статистики и заменю ее комментариями. Наша статья не о RAS, а об ООП. У меня есть книжка, название которой приводить не буду. В ней на протяжении нескольких сотен страниц разжевываются три основных понятия ООП —инкапсуляция, наследование и полиморфизм. Четыреста страниц и неприменимый на практике код. Мы же в ЭТОЙ СТАТЬЕ понятно и четко рассмотрим все три кита ООП таким образом, что многотомные труды по этому вопросу можно будет использовать вместо подставки для акустических колонок — чтоб вибрация дурно на компьютер не влияла.

Вначале об инкапсуляции. Слово довольно гнусное, зато емкое. Без него никак. Можно, конечно, придумать нечто вроде «внутрьсокрытие», но это будет намного хуже. Опишем базовый абстрактный класс, который будет представлять нам статистические данные и обладать функциями их получения.

Абстрактный класс — это класс, который содержит абстрактные методы. А абстрактные методы — это функции или процедуры, которые не имеют реализации. Просто имена и параметры, ничего больше.

В нашем примере мы описали класс CAStats. У него есть поля speed (скорость соединения), bytes_received (количество принятых байт), bytes_transmitted (количество переданных байт) и процедура GetStats, которая по идее должна получать статистику извне и заполнять вышеупомянутые поля полученными значениями. Однако эта процедура объявлена у нас как абстрактная, поэтому физически ничего не делает. Можете считать, что наш абстрактный класс — это шаблон для реальных классов, созданных на его основе. Прежде чем мы приступим к их созданию, подумайте вот о чем: только что мы прошли такую «важную» тему как инкапсуляция. То есть мы воплотили в классе некое понятие — статистику текущего соединения с провайдером. Это и есть частный случай инкапсуляции.

На самом деле понятие инкапсуляции несколько тоньше и обширнее — например, оно охватывает такие вещи как области видимости членов (полей и методов), но это выходит за рамки статьи. Займемся другим аспектом ООП —наследованием. Создадим на основе класса CAStats два класса-потомка —CWin9xStats и CW2KStats. Первый класс будет воплощать в себе методы получения статистики для Windows 95/98/Me, а второй — для Windows NT/2000/XP. Немного кода:

Как видно, чтобы указать, от какого класса мы наследуем новый класс, достаточно в объявлении нового класса указать после ключевого слова class имя класса-предка, заключив его в круглые скобки. Кстати, C++ поддерживает множественное наследование, при котором класс-потомок может иметь несколько предков. А в Object Pascal такого чуда нет.

Что получается при наследовании? Поля и функции предка переходят «по наследству» в класс-потомок. То есть, класс CWin9xStats получает поля speed, bytes_received, bytes_transmitted и метод GetStats. CW2KStats унаследовал то же самое. Чем должны отличаться эти классы? Процедурой GetStats — ведь способы получения статистики у каждого нашего класса разные. GetStats из CWin9xStats читает данные из Реестра и заполняет ими поля speed, bytes_received и bytes_transmitted, а GetStats класса CW2KStats берет статистику с помощью функции RasGetConnectionStatistics и тоже заполняет поля speed, bytes_received и bytes_transmitted. Чтобы переопределить виртуальный абстрактный метод GetStats, мы использовали в каждом классе директиву override (переопределить):

Если бы мы не объявили эту процедуру как override, а написали бы просто procedure GetStats, то этим мы декларировали бы НОВУЮ процедуру GetStats и не смогли бы воспользоваться тем, о чем я расскажу дальше — ПОЛИМОРФИЗМОМ.

Пока же пусть вас согревает мысль, что и наследование — пройденный этап. Фактически в наследовании надо знать еще одну вещь. Что происходит при наследовании с членами классов, которые имеют разную область видимости? Дело в том, что поля и методы могут быть объявлены в разных секциях класса —public, private, protected.

К членам, имеющим область видимости private, невозможно обращаться ИЗВНЕ класса. Они доступны только внутри процедур и функций класса, в котором объявлены. Protected, коротко говоря, видны в пределах модуля, где объявлен класс. Public видны везде. В классе-потомке можно переопределять область видимости члена, например, делать из private public. На практике эта операция используется довольно часто — например, в неком классе есть нужное вам поле, однако оно является private. Просто создаете потомка для этого класса и переобъявляете поле как public, таким образом получая к нему доступ. Притом вам не нужно даже создавать экземпляр нового класса — воспользуйтесь старым, но примените операцию приведения типа.

Небольшой пример. Был класс A, в нем private-поле money. Делаем класс B, объявляем в нем money как public. Еще у нас есть экземпляр класса A. Называется, допустим, bank. Мы не можем обратиться к bank.money, потому что bank все-таки экземпляр класса A, где поле money приватно. Но мы вполне можем привести A к типу B (операция приведения типа), написав нечто вроде:

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

Но вернемся к ООП. Нам осталось лишь заглянуть в глаза полиморфизму. Если вы играли в Baldur's Gate, то помните, что там есть такой класс персонажей — полиморфы. Полиморф — это тот, кто имеет много форм. «Поли» по-гречески означает «много», а «морф» — форма, вот и вся премудрость. Оборотни полиморфны по своей природе. В Baldur's Gate вы могли превращаться в медведя, волка, обратно в человека, но управляли персонажем по-прежнему мышью. Эдакое универсальное средство управления. Ему безразлично, как выглядит персонаж, оно просто дает сигнал двигаться, атаковать и так далее.

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

Теперь, в зависимости от версии Windows, создадим экземпляр либо класса CWin9xStats, либо CW2KStats. На псевдокоде это выглядит так:

В прозе это выражается так. Переменная stats может становиться экземпляром как класса CWin9xStats, так и CW2KStats — настоящий оборотень. Поэтому мы делаем только ОДНУ проверку версии системы, в соответствии с этим создаем экземпляр нужного нам класса, и далее в нашей гипотетической программе stats работает уже как реальный экземпляр CWin9xStats или CW2KStats.

Например, у нас Windows 98. Когда мы напишем в коде stats.GetStats, то будет вызван метод GetStats класса CWin9xStats. А если у нас Windows XP, то вызовется GetStats класса CW2KStats. За счет чего достигается этот эффект? За счет того, что GetStats — виртуальный метод. Не зря мы объявляли его как virtual. Без этого мы не смогли бы его переопределить (override), оставив то же название, однако в теле процедуры написав совершенно другой код.

Итак, наиболее традиционное применение полиморфизма заключается в следующем. Сначала мы описываем как бы класс-шаблон, с виртуальными и желательно абстрактными методами. Затем создаем классы-потомки, наследников базового абстрактного класса. В них могут быть дополнительные поля и методы, которых нет в классе-предке. Но помните, что механизм полиморфизма позволяет вам вызывать лишь те члены класса, которые объявлены в предке. Иными словами, если вы добавите в класс CW2KStats метод more_statistics (больше статистики), то не сможете обратиться к нему в коде как stats.more_statistics, потому что stats у нас имеет тип CAStats, который знать не знает ни о каком more_statistics. Однако внутри методов CW2KStats мы вполне свободно можем вызывать more_statistics.

Рассматривайте базовый класс как своего рода общий интерфейс, через который вы можете взаимодействовать с классами-потомками. Пусть все общение с «внешним миром» осуществляется через виртуальные методы, «стандартизированные» в общем предке. Например, если вы пишете музыкальный плейер, то можете создать базовый класс CAPlayer, у которого будет виртуальный абстрактный метод PlayFile(filename:string). Затем создайте классы-потомки вроде COggPlayer, CMP3Player, CWAVPlayer — каждый со своей реализацией метода PlayFile. Затем объявите переменную player типа CAPlayer, а также экземпляры классов COggPlayer, CMP3Player и CWAVPlayer:

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

Иными словами, мы в зависимости от конкретной ситуации — формата файла — присваиваем переменной player экземпляр нужного нам класса. Можете возразить, мол, зачем именно так, ведь можно просто вызывать OggPlayer.PlayFile, MP3Player.PlayFile и так далее. Но я привел упрощенный пример. В реальном плейере, поддерживающем плагины для общения с разными форматами файлов, вы бы создали список или коллекцию экземпляров некоего класса-потомка, в методе PlayFile которого вызывали бы из dll'ки плагина функцию воспроизведения звука. И никаких «имен собственных» в наименовании экземпляров не было бы. Исходя из формата файла вы бы извлекали из коллекции (читай — массива) экземпляр, созданный на лету на основе плагина, который понимает этот формат. Найденный экземпляр вам останется лишь присвоить переменной player, после чего пользуйтесь им на здоровье —player.PlayFile(filename). Вот что такое полиморфизм.

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

Рекомендуем ещё прочитать:






Данную страницу никто не комментировал. Вы можете стать первым.

Ваше имя:
Ваша почта:

RSS
Комментарий:
Введите символы или вычислите пример: *
captcha
Обновить





Хостинг на серверах в Украине, США и Германии. © sector.biz.ua 2006-2015 design by Vadim Popov