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 ГОД

Снова ООП-ля!!!

Сергей РОГАТКИН perfolenta@nm.ru

Концепция объектно-ориентированного программирования (ООП) позволяет упростить создание сложных программ. В том или ином виде ее использует сегодня каждый программист. Но в нашем полку ежедневно прибывает, и все новые и новые братья по клавиатуре пытаются разобраться в том, что же такое ООП. А разобраться трудно. Статьи в популярных журналах (например, Петр «Roxton» Семилетов — «ООП-ля!», МК №15(238) 14-21.04.2003) однобоки и поверхностны, а толстые книги уводят в дебри теории (и философии) программирования и требуют больших усилий для изучения. Надеюсь, что предлагаемая статья поможет разобраться в данном вопросе, хотя и не призвана стать для вдумчивого читателя единственным источником информации. Она вряд ли заменит хорошую книгу, но должна упорядочить мысли и дать общее представление.

Итак, что же такое ООП? Для полного понимания причин появления ООП рассмотрим историю вопроса. Когда-то давным-давно, когда памяти у компьютеров было мало, а получить от компьютера хотелось много, программирование было ЛИНЕЙНЫМ. Т.е. программист писал программу последовательно от начала и до конца, не дробя ее на какие-либо функциональные части, попутно стараясь изо всех сил оптимизировать размер кода. Для достижения высокой степени оптимизации некоторые куски кода многократно вызываются в хаотическом порядке (этот порядок, конечно, имеет логику, но почти недоступную для понимания посторонних) из других мест программы. Этот стиль программирования часто называют «взрыв на макаронной фабрике», т.к. программа в результате напоминает запутанный клубок нитей. Отладка и сопровождение таких сверхоптимизированных программ с ростом возможностей компьютеров и размеров программ постепенно превратилась в настоящую му[ударение!!!!!!!!]ку. Теоретики задумались… Эволюция тронулась… На сцену вышло структурное программирование (Петр Семилетов в своей статье назвал его «процедурным», что терминологически неверно, т.к. ООП также является частным случаем процедурного программирования).

Лирическое отступление

Существуют две группы языков программирования: процедурные и директивные. На процедурных языках программист объясняет машине, КАК найти решение задачи. К этим языкам относятся большинство языков, например Basic, Fortran, C, C++, Pascal, Java и др. На директивных языках программист задает набор исходных данных, связи и отношения между ними, а также ЧТО необходимо решить. КАК найти решение — забота компьютера на пару с компилятором. Самым известным представителем является язык программирования Prolog.

Конец лирического отступления

Из известных специалистов, работавших над теорией структурного программирования, пожалуй, особо выделяется голландский профессор Э. В. Дейкстра. Им было предложено оставить в употреблении только три основных конструкции —следование, повторение и выбор, а вредный оператор безусловного перехода GO TO (который, по мнению теоретиков, и превращал программы в «тарелки с макаронами») поначалу выбросили совсем. Основным принципом стало разбиение программ на небольшие независимые блоки, каждый из которых можно написать и отладить отдельно. Теперь создание программы начиналось вовсе не с программирования, а с анализа задачи с целью оптимального разбиения ее на отдельные функционально законченные блоки, и только затем каждый из этих блоков алгоритмизировался, программировался и отлаживался. Уже на этом этапе появляется важное для ООП понятие инкапсуляция (Encapsulation). Функционально законченные блоки, однажды написанные и отлаженные, могут свободно использоваться в дальнейшем, скрывая (инкапсулируя) внутренние детали реализации данного блока. Если не менять входные и выходные параметры (стандартизировать их), то внутренний алгоритм блока может изменяться и совершенствоваться его авторами, а программисты, которые используют данный блок, могут ничего не знать (и не обязаны знать) о его внутреннем устройстве. Инкапсуляция позволила программистам мыслить в терминах взаимодействия блоков, которые можно комбинировать между собой и легко оперировать их взаимосвязями.

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

Лирическое отступление

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

Конец лирического отступления

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

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

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

если существуют общие процедуры для некоторой группы разнотипных объектов. Например, классы Зверь, Воин, Оружие и ПолеБитвы могут быть унаследованы от класса ЭлементыИгры для обеспечения сохранения и восстановления их состояния на диске единообразным способом;

Вы не обязаны (но можете) использовать наследование реализации в следующих случаях:

если вы нуждаетесь только в одном методе базового класса;

если вам в дальнейшем придется переопределить все методы базового класса.

Лирическое отступление:

В упомянутой выше статье Петр Семилетов привел допустимый, но неудачный пример для демонстрации наследования. В его случае следовало бы создать класс, который на разных операционных системах работал бы одинаково с точки зрения использующего его программиста, тогда этот пример хорошо подошел бы для демонстрации инкапсуляции. Мы скрываем от пользователей различия в работе класса на разных ОС, но позволяем им использовать его, не задумываясь о деталях реализации. Проверку версии ОС можно осуществить только один раз — при создании экземпляра класса. Автор усложнил себе жизнь, создав три класса вместо одного. Второе замечание касается того, что Петр унаследовал только интерфейс базового класса, но не его реализацию, т.е. фактически данный пример приведен для демонстрации полиморфизма, а не наследования функциональности предка. Третье замечание состоит в том, что в его примере переменная stats НЕ может становиться экземпляром классов CWin9xStats или CW2KStats, как он об этом пишет. Наоборот, переменная stats позволяет получить доступ к классу CAStats (исключительно к членам определенным в CAStats), являющемуся частью классов потомков CWin9xStats и CW2KStats. Это замечание подтверждается ниже по тексту, где прямо указано, что переменная stats имеет тип CAStats и не позволяет вызвать метод класса CWin9xStats more_statistics. Советую перечитать статью Петра и разобраться с неточностями, допущенными в его статье.

Конец лирического отступления

Принципы наследования сходны во всех языках программирования, но полнота их реализации сильно варьируется от языка к языку. Например, в С++ класс может иметь несколько предков, а в Object Pascal нет, в Visual Basic 6 вы можете унаследовать в одном классе интерфейсы множества предков, но не можете унаследовать реализацию методов предков. В языках, живущих на платформе .Net, можно применять оба типа наследования — и наследование интерфейса (Implements), и наследование реализации (Inherits), смотря по тому, в каком случае что удобнее.

С понятием наследования тесно связано последнее важное понятие ООП —полиморфизм (Polymorphism). В самом общем случае полиморфизм позволяет одному объекту выглядеть для компилятора как другой объект. Т.е. метод Save у разных объектов может действовать совершенно по-разному, но компилятор сможет правильно вызвать метод Save для любого объекта, который является полиморфным. Это очень удобно: вы можете написать программу, оперирующую одним объектом, а затем подставить ей другой тип объекта, и она, не заметив подвоха, обработает его таким же образом, как и «родной» для нее объект. В статье Семилетова рассмотрен хороший пример использования полиморфизма при разработке музыкального плейера. Каждый плагин является объектом, имеющим методы OpenFile, Play и Stop. Программа плейера оперирует объектом Player и вызывает данные методы, совершенно не зная, какой плагин в данном случае скрывается под именем Player. В результате программа получается простой и понятной.

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

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

Дерзайте!

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






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

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

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





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