Что такое ООП и с чем его едят? Принципы объектно-ориентированного программирования Основе объектно ориентированного программирования лежит

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

Две методики

Все компьютерные программы состоят из двух элементов: кода и данных. Более того, программа концептуально может быть организована вокруг своего кода или своих данных. Иными словами, организация одних программ определяется тем, “что происходит”, а других - тем, “на что оказывается влияние”. Существуют две методики создания программ. Первая из них называется моделью, ориентированной на процессы и характеризует программу как последовательность линейных шагов (т.е. кода). Модель, ориентированную на процессы, можно рассматривать в каче­стве кода, воздействующего на данные. Такая модель довольно успешно применяется в процедурных языках вроде С. Но, как отмечалось в главе 1, подобный подход по­рождает ряд трудностей в связи с увеличением размеров и сложности программ.

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

Абстракция

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

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

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

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

Три принципа ООП

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

Инкапсуляция

Механизм, связывающий код и данные, которыми он манипулирует, защищая оба эти компонента от внешнего вмешательства и злоупотреблений, называется инкап­суляцией. Инкапсуляцию можно считать защитной оболочкой, которая предохраня­ет код и данные от произвольного доступа со стороны другого кода, находящегося снаружи оболочки. Доступ к коду и данным, находящимся внутри оболочки, строго контролируется тщательно определенным интерфейсом. Чтобы провести анало­гию с реальным миром, рассмотрим автоматическую коробку передач автомобиля. Она инкапсулирует немало сведений об автомобиле, в том числе величину ускоре­ния, крутизну поверхности, по которой совершается движение, а также положение рычага переключения скоростей. Пользователь (в данном случае водитель) может оказывать влияние на эту сложную инкапсуляцию только одним способом: переме­щая рычаг переключения скоростей. На коробку передач нельзя воздействовать, например, с помощью индикатора поворота или дворников. Таким образом, рычаг переключения скоростей является строго определенным, а по существу, единствен­ным, интерфейсом с коробкой передач. Более того, происходящее внутри коробки передач не влияет на объекты, находящиеся вне ее. Например, переключение пере­дач не включает фары! Функция автоматического переключения передач инкапсу­лирована, и поэтому десятки изготовителей автомобилей могут реализовать ее как угодно. Но с точки зрения водителя все эти коробки передач работают одинаково. Аналогичный принцип можно применять и в программировании. Сильная сторона инкапсулированного кода состоит в следующем: всем известно, как получить доступ к нему, а следовательно, его можно использовать независимо о подробностей реали­зации и не опасаясь неожиданных побочных эффектов.

Основу инкапсуляции ejavaсоставляет класс. Подробнее классы будут рассмотре­ны в последующих главах, а до тех пор полезно дать хотя бы краткое их описание. Класс определяет структуру и поведение (данные и код), которые будут совместно использоваться набором объектов. Каждый объект данного класса содержит струк­туру и поведение, которые определены классом, как если бы объект был “отлит” в форме класса. Поэтому иногда объекты называют экземплярами класса. Таким об­разом, класс - это логическая конструкция, а объект - ее физическое воплощение.

При создании класса определяются код и данные, которые образуют этот класс. Совместно эти элементы называются членами класса. В частности, определенные в классе данные называются перемени ымичленами, или переменными экземпляра, а код, оперирующий данными, - методами-членами, или просто методами. (То, что програм­мирующие на Java называют методами, программирующие на C/C++ называют функ­циями) В программах, правильно написанных на Java, методы определяют, каким об­разом используются переменные-члены. Это означает, что поведение и интерфейс класса определяются методами, оперирующими данными его экземпляра.

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

Рис. 1.

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

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

Как правило, большинство людей воспринимают окружающий мир в виде ие­рархически связанных между собой объектов, подобных животным, млекопитаю­щим и собакам. Если требуется привести абстрактное описание животных, можно сказать, что они обладают определенными свойствами: размеры, уровень интел­лекта и костная система. Животным присущи также определенные особенности поведения: они едят, дышат и спят. Такое описание свойств и поведения составля­ет определение класса животных.

Если бы потребовалось описать более конкретный класс животных, например млекопитающих, следовало бы указать более конкретные свойства, в частности тип зубов и молочных желез. Такое определение называется подклассом животных, которые относятся к суперклассу (родительскому классу) млекопитающих. А поскольку млекопитающие - лишь более точно определенные животные, то они на­следуют все свойства животных. Подкласс нижнего уровня иерархии классов наследу­ет все свойства каждого из его родительских классов (рис. 2).

Рис. 2.

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

Рис. 3. Лабрадор полностью наследует инкапсулированные свойства всех родительских классов животных

Полиморфизм

Полиморфизм (от греч. “много форм”) - это принцип ООП, позволяющий ис­пользовать один и тот же интерфейс для общего класса действий. Каждое действие зависит от конкретной ситуации. Рассмотрим в качестве примера стек, действую­щий как список обратного магазинного типа. Допустим, в программе требуются стеки трех типов: для целочисленных значений, для числовых значений с плаваю­щей точкой и для символов. Алгоритм реализации каждого из этих стеков остается неизменным, несмотря на отличия в данных, которые в них хранятся. В языке, не являющемся объектно-ориентированным, для обращения со стеком пришлось бы создавать три разных ряда служебных программ под отдельными именами. A ejava, благодаря принципу полиморфизма, для обращения со стеком можно определить общий ряд служебных программ под одними и теми же общими именами.

В более общем смысле принцип полиморфизма нередко выражается фразой “один интерфейс, несколько методов”. Это означает, что можно разработать об­щий интерфейс для группы связанных вместе действий. Такой подход позволяет уменьшить сложность программы, поскольку один и тот же интерфейс служит для указания общего класса действий. А выбор конкретного действия (т.е. метода) де­лается применительно к каждой ситуации и входит в обязанности компилятора. Это избавляет программиста от необходимости делать такой выбор вручную. Ему нужно лишь помнить об общем интерфейсе и правильно применять его.

Если продолжить аналогию с собаками, то можно сказать, что собачье обоня­ние - полиморфное свойство. Если собака почувствует запах кошки, она залает и погонится за ней. А если собака почувствует запах своего корма, то у нее нач­нется слюноотделение, и она поспешит к своей миске. В обоих случаях действует одно и то же чувство обоняния. Отличие лишь в том, что именно издает запах, т.е. в типе данных, воздействующих на нос собаки! Этот общий принцип можно реализовать, применив его к методам в программе на Java.

Совместное применение полиморфизма, инкапсуляции и наследования

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

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

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

И наконец, полиморфизм ясно отражает способность изготовителей автомо­билей предлагать большое разнообразие вариантов, по сути, одного и того же средства передвижения. Так, на автомобиле могут быть установлены система тор­мозов с защитой от блокировки или традиционные тормоза, рулевая система с гидроусилителем или с реечной передачей и 4-, 6- или 8-цилиндровые двигатели. Но в любом случае придется нажать на педаль тормоза, чтобы остановиться, вращать руль, чтобы повернуть, и нажать на педаль акселератора, чтобы автомобиль дви­гался быстрее. Один и тот же интерфейс может быть использован для управления самыми разными реализациями.

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

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

Я не умею программировать на объектно-ориентированных языках. Не научился. После 5 лет промышленного программирования на Java я всё ещё не знаю, как создать хорошую систему в объектно-ориентированном стиле. Просто не понимаю.

Я пытался научиться, честно. Я изучал паттерны, читал код open source проектов, пытался строить в голове стройные концепции, но так и не понял принципы создания качественных объектно-ориентированных программ. Возможно кто-то другой их понял, но не я.

И вот несколько вещей, которые вызывают у меня непонимание.

Я не знаю, что такое ООП

Серьёзно. Мне сложно сформулировать основные идеи ООП. В функциональном программировании одной из основных идей является отсутствие состояния. В структурном - декомпозиция. В модульном - разделение функционала в законченные блоки. В любой из этих парадигм доминирующие принципы распространяются на 95% кода, а язык спроектирован так, чтобы поощрять их использование. Для ООП я таких правил не знаю.
  • Абстракция
  • Инкапсуляция
  • Наследование
  • Полиморфизм
Смахивает на свод правил, не так ли? Значит вот оно, те самые правила, которым нужно следовать в 95% случаев? Хмм, давайте посмотрим поближе.

Абстракция

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

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

Во-вторых, абстракции в программировании были всегда, начиная с записей Ады Лавлейс, которую принято считать первым в истории программистом. С тех пор люди бесперерывно создавали в своих программах абстракции, зачастую имея для этого лишь простейшие средства. Так, Абельсон и Сассман в своей небезызвестной книге описывают, как создать систему решения уравнений с поддержкой комплексных чисел и даже полиномов, имея на вооружении только процедуры и связные списки. Так какие же дополнительные средства абстрагирования несёт в себе ООП? Понятия не имею. Выделение кода в подпрограммы? Это умеет любой высокоуровневый язык. Объединение подпрограмм в одном месте? Для этого достаточно модулей. Типизация? Она была задолго до ООП. Пример с системой решения уравнений хорошо показывает, что построение уровней абстракции не столько зависит от средств языка, сколько от способностей программиста.

Инкапсуляция

Главный козырь инкапсуляции в сокрытии реализации. Клиентский код видит только интерфейс, и только на него может рассчитывать. Это развязывает руки разработчикам, которые могут решить изменить реализацию. И это действительно круто. Но вопрос опять же в том, причём тут ООП? Все вышеперечисленные парадигмы подразумевают сокрытие реализации. Программируя на C вы выделяете интерфейс в header-файлы, Oberon позволяет делать поля и методы локальными для модуля, наконец, абстракция во многих языках строится просто посредствам подпрограмм, которые также инкапсулируют реализацию. Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции , предоставляя доступ к данным через специальные методы - getters и setters в Java, properties в C# и т.д. (В комментариях выяснили, что некоторые объекты в языках программирования не являются объектами с точки зрения ООП: data transfer objects отвечают исключительно за перенос данных, и поэтому не являются полноценными сущностями ООП, и, следовательно, для них нет необходимости сохранять инкапсуляцию. С другой стороны, методы доступа лучше сохранять для поддержания гибкости архитектуры. Вот так всё непросто.) Более того, некоторые объектно-ориентированные языки, такие как Python, вообще не пытаются что-то скрыть, а расчитывают исключительно на разумность разработчиков, использующих этот код.

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

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

Значит, вот оно - то, что выделяет ООП как парадигму среди других? Хмм… если так, то почему мы так редко используем его в реальном коде? Помните, я говорил про 95% кода, подчиняющихся правилам доминирующей парадигмы? Я ведь не шутил. В функцинальном программировании не меньше 95% кода использует неизменяемые данные и функции без side-эффектов. В модульном практически весь код логично расфасован по модулям. Преверженцы структурного программирования, следуя заветам Дейкстры, стараются разбивать все части программы на небольшие части. Наследование используется гораздо реже. Может быть в 10% кода, может быть в 50%, в отдельных случаях (например, при наследовании от классов фреймворка) - в 70%, но не больше. Потому что в большинстве ситуаций это просто не нужно .

Более того, наследование опасно для хорошего дизайна. Настолько опасно, что Банда Четырех (казалось бы, проповедники ООП) в своей книге рекомендуют при возможности заменять его на делегирование. Наследование в том виде, в котором оно существует в популярных ныне языках ведёт к хрупкому дизайну. Унаследовавшись от одного предка, класс уже не может наследоваться от других. Изменение предка так же становится опасным. Существуют, конечно, модификаторы private/protected, но и они требуют неслабых экстрасенсорных способностей для угадывания, как класс может измениться и как его может использовать клиентский код. Наследование настолько опасно и неудобно, что крупные фреймворки (такие как Spring и EJB в Java) отказываются от них, переходя на другие, не объектно-ориентированные средства (например, метапрограммирование). Последствия настолько непредсказуемы, что некоторые библиотеки (такие как Guava) прописывает своим классам модификаторы, запрещающие наследование, а в новом языке Go было решено вообще отказаться от иерархии наследования.

Полиморфизм

Пожалуй, полиморфизм - это лучшее, что есть в объектно-ориентированном программировании. Благодаря полиморфизму объект типа Person при выводе выглядит как «Шандоркин Адам Имполитович», а объект типа Point - как "". Именно он позволяет написать «Mat1 * Mat2» и получить произведение матриц, аналогично произведению обычных чисел. Без него не получилось бы и считывать данные из входного потока, не заботясь о том, приходят они из сети, файла или строки в памяти. Везде, где есть интерфейсы, подразумевается и полиморфизм.

Мне правда нравится полиморфизм. Поэтому я даже не стану говорить о его проблемах в мейнстримовых языках. Я также промолчу про узость подхода диспетчеризации только по типу, и про то, как это могло бы быть сделано . В большинстве случаев он работает как надо, а это уже неплохо. Вопрос в другом: является ли полиморфизм тем самым принципом, отличающим ООП от других парадигм? Если бы вы спросили меня (а раз уж вы читаете этот текст, значит, можно считать, что спросили), я бы ответил «нет». И причина всё в тех же процентах использования в коде. Возможно, интерфейсы и полиморфные методы встречаются немного чаще наследования. Но сравните количество строк кода, занимаемое ими, с количеством строк, написанных в обычном процедурном стиле - последних всегда больше. Глядя на языки, поощряющие такой стиль программирования, я не могу назвать их полиморфными. Языки с поддержкой полиморфизма - да, так нормально. Но не полиморфные языки.

(Впрочем, это моё мнение. Вы всегда можете не согласиться.)

Итак, абстракция, инкапсуляция, наследование и полиморфизм - всё это есть в ООП, но ничто из этого не является его неотъемлемым атрибутом. Тогда что такое ООП? Есть мнение, что суть объектно-ориентированного программирования лежит в, собственно, объектах (звучит вполне логично) и классах. Именно идея объединения кода и данных, а также мысль о том, что объекты в программе отражают сущности реального мира. К этому мнению мы ещё вернёмся, но для начала расставим некоторые точки над i.

Чьё ООП круче?

Из предыдущей части видно, что языки программирования могут сильно отличаться по способу реализации объектно-ориентированного программирования. Если взять совокупность всех реализаций ООП во всех языках, то вероятнее всего вы не найдёте вообще ни одной общей для всех черты. Чтобы как-то ограничить этот зоопарк и внести ясность в рассуждения, я остановлюсь только одной группе - чисто объекто-ориентированные языки, а именно Java и C#. Термин «чисто объектно-ориентированный» в данном случае означает, что язык не поддерживает другие парадигмы или реализует их через всё то же ООП. Python или Ruby, например, не буду являться чистыми, т.к. вы вполне можете написать полноценную программу на них без единого объявления класса.

Чтобы лучше понять суть ООП в Java и C#, пробежимся по примерам реализации этой парадигмы в других языках.

Smalltalk. В отличие от своих современных коллег, этот язык имел динамическую типизацию и использовал message-passing style для реализации ООП. Вместо вызовов методов объекты посылали друг другу сообщения, а если получатель не мог обработать то, что пришло, он просто пересылал сообщение кому-то ещё.

Common Lisp. Изначально CL придерживался такой же парадигмы. Затем разработчики решили, что писать `(send obj "some-message)` - это слишком долго, и преобразовали нотацию в вызов метода - `(some-method obj)`. На сегодняшний день Common Lisp имеет развитую систему объектно-ориентированного программирования (CLOS) с поддержкой множественного наследования, мультиметодов и метаклассов. Отличительной чертой является то, что ООП в CL крутится не вокруг объектов, а вокруг обобщённых функций.

Clojure. Clojure имеет целых 2 системы объектно-ориентированного программирования - одну, унаследованную от Java, и вторую, основанную на мультиметодах и более похожую на CLOS.

R. Этот язык для статистического анализа данных также имеет 2 системы объектно-ориентированного программирования - S3 и S4. Обе унаследованы от языка S (что не удивительно, учитывая, что R - это open source реализация коммерческого S). S4 по большей части соотвествует реализациям ООП в современных мейнстримовых языках. S3 является более легковесным вариантом, элементарно реализуемым средствами самого языка: создаётся одна общая функция, диспетчеризирующая запросы по атрибуту «class» полученного объекта.

JavaScript. По идеологии похож на Smalltalk, хотя и использует другой синтаксис. Вместо наследования использует прототипирование: если искомого свойства или вызванного метода в самом объекте нет, то запрос передаётся объекту-прототипу (свойство prototype всех объектов JavaScript). Интересным является факт, что поведение всех объектов класса можно поменять, заменив один из методов прототипа (очень красиво, например, выглядит добавление метода `.toBASE64` для класса строки).

Python. В целом придерживается той же концепции, что и мейнcтримовые языки, но кроме этого поддерживает передачу поиска атрибута другому объекту, как в JavaScript или Smalltalk.

Haskell. В Haskell вообще нет состояния, а значит и объектов в обычном понимании. Тем не менее, своеобразное ООП там всё-таки есть: типы данных (types) могут принадлежать одному или более классам типов (type classes). Например, практически все типы в Haskell состоят в классе Eq (отвечает за операции сравнения 2-х объектов), а все числа дополнительно в классах Num (операции над числами) и Ord (операции <, <=, >=, >). В менстримовых языках типам соответствуют классы (данных), а классам типов - интерфейсы.

Stateful или Stateless?

Но вернёмся к более распространённым системам объектно-ориентированного программирования. Чего я никогда не мог понять, так это отношения объектов с внутренним состоянием. До изучения ООП всё было просто и прозрачно: есть структуры, хранящие несколько связанных данных, есть процедуры (функции), их обрабатывающие. выгулять(собаку), снятьс(аккаунт, сумма). Потом пришли объекты, и это было тоже ничего (хотя читать программы стало гораздо сложней - моя собака выгуливала [кого?], а аккаунт снимал деньги [откуда?]). Затем я узнал про сокрытие данных. Я всё ещё мог выгулять собаку, но вот посмотреть состав её пищи уже не мог. Пища не выполняла никаких действий (наверное, можно было написать, что пища.съесть(собака), но я всё-таки предпочитаю, чтобы моя собака ела пищу, а не наоборот). Пища - это просто данные, а мне (и моей собаке) нужно было просто получить к ним доступ. Всё просто . Но в рамки парадигмы влезть было уже невозможно, как в старые джинсы конца 90-х.

Ну ладно, у нас есть методы доступа к данным. Пойдём на этот маленький самообман и притворимся, что данные у нас действительно скрыты. Зато я теперь знаю, что объекты - это в первую очередь данные, а потом уже, возможно, методы их обрабатывающие. Я понял, как писать программы, к чему нужно стремиться при проектировании.

Не успел я насладиться просветлением, как увидил в интернетах слово stateless (готов поклясться, оно было окружено сиянием, а над буквами t и l висел нимб). Короткое изучение литературы открыло чудесный мир прозрачного потока управления и простой многопоточности без необходимости отслеживать согласованность объекта. Конечно, мне сразу захотелось прикоснуться к этому чудесному миру. Однако это означало полный отказ от любых правил - теперь было непонятно, следует ли собаке самой себя выгуливать, или для этого нужен специальный ВыгулМенеджер; нужен ли аккаунт, или со всей работой справится Банк, а если так, то должен он списывать деньги статически или динамически и т.д. Количество вариантов использования возрасло экспоненциально, и все варианты в будущем могли привести к необходимости серьёзного рефакторинга.

Я до сих пор не знаю, когда объект следует сделать stateless, когда stateful, а когда просто контейнером данных. Иногда это очевидно, но чаще всего нет.

Типизация: статическая или динамическая?

Еща одна вещь, с которой я не могу определиться относительно таких языков, как C# и Java, это являются они статически или динамически типизированными. Наверное большинство людей воскликнет «Что за глупость! Конечно статически типизированными! Типы проверяются во время компиляции!». Но действительно ли всё так просто? Правда ли, что программист, прописывая в параметрах метода тип X может быть уверен, что в него всегда будут передаваться объекты именно типа X? Верно - не может, т.к. в метод X можно будет передать параметр типа X или его наследника . Казалось бы, ну и что? Наследники класса X всё равно будут иметь те же методы, что и X. Методы методами, а вот логика работы может оказаться совершенно другой. Самый распространённый случай, это когда дочерний класс оказывается соптимизированным под другие нужды, чем X, а наш метод может рассчитывать именно на ту оптимизацию (если вам такой сценарий кажется нереалистичным, попробуйте написать плагин к какой-нибудь развитой open source библиотеке - либо вы потратите несколько недель на разбор архитектуры и алгоритмов библиотеки, либо будете просто наугад вызывать методы с подходящей сигнатурой). В итоге программа работает, однако скорость работы падает на порядок. Хотя с точки зрения компилятора всё корректно. Показательно, что Scala, которую называют наследницей Java, во многих местах по умолчанию разрешает передавать только аргументы именно указанного типа, хотя это поведение и можно изменить.

Другая проблема - это значение null, которое может быть передано практически вместо любого объекта в Java и вместо любого Nullable объекта в C#. null принадлежит сразу всем типам, и в то же время не принадлежит ни одному. null не имеет ни полей, ни методов, поэтому любое обращение к нему (кроме проверки на null) приводит к ошибке. Вроде бы все к этому привыкли, но для сравнения Haskell (да и та же Scala) заставлют использовать специальные типы (Maybe в Haskell, Option в Scala) для обёртки функций, которые в других языках могли бы вернуть null. В итоге про Haskell часто говорят «скомпилировать программу на нём сложно, но если всё-таки получилось, значит скорее всего она работает корректно».

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

Какая разница, как называется такая типизация, если все правила всё равно известны? Разница в том, с какой стороны подходить к проектированию архитектуры. Существует давний спор, как строить систему: делать много типов и мало функций, или мало типов и много функций? Первый подход активно используется в Haskell, второй в Lisp. В современных объектно-ориентированных языках используется что-то среднее. Я не хочу сказать, что это плохо - наверное у него есть свои плюсы (в конце концов не стоит забывать, что за Java и C# стоят мультиязыковые платформы), но каждый раз приступая к новому проекту я задумываюсь, с чего начать проектирования - с типов или с функционала.

И ещё...

Я не знаю, как моделировать задачу. Считается, что ООП позволяет отображать в программе объекты реального мира. Однако в реальности у меня есть собака (с двумя ушами, четырмя лапами и ошейником) и счёт в банке (с менеджером, клерками и обеденным перерывом), а в программе - ВыгулМенеджер, СчётФабрика… ну, вы поняли. И дело не в том, что в программе есть вспомогательные классы, не отражающие объекты реального мира. Дело в том, что поток управления изменяется . ВыгулМенеджер лишает меня удовольствия от прогулки с собакой, а деньги я получаю от бездушного БанкСчёта (эй, где та милая девушка, у которой я менял деньги на прошлой неделе?).

Может быть я сноб, но мне было гораздо приятней, когда данные в компьютере были просто данными, даже если описывали мою собаку или счёт в банке. С данными я мог сделать то, что удобно, без оглядки на реальный мир.

Я также не знаю, как правильно декомпозировать функционал. В Python или C++, если мне нужна была маленькая функция для преобразования строки в число, я просто писал её в конце файла. В Java или C# я вынужден выносить её в отдельный класс StringUtils. В недо-ОО-языках я мог объявить ad hoc обёртку для возврата двух значений из функции (снятую сумму и остаток на счету). В ООП языках мне придётся создать полноценный класс РезультатТранзакции. И для нового человека на проекте (или даже меня самого через неделю) этот класс будет выглядеть точно таким же важным и фундаментальным в архитектуре системы. 150 файлов, и все одинаково важные и фундаментальные - о да, прозрачная архитектура, прекрасные уровни абстракции.

Я не умею писать эффективные программы. Эффективные программы используют мало памяти - иначе сборщик мусора будет постоянно тормозить выполнение. Но чтобы совершить простейшую операцию в объектно-ориентированных языках приходится создавать дюжину объектов. Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли. В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект - для хранения результата. В ООП мне приходится засорять память постоянно.

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

Основные принципы и этапы объектно-ориентированного

программирования

В теории программирования ООП определяется как технология создания сложного программного обеспечения, которая основана на представлении программы в виде совокупности объектов , каждый из которых является экземпляром определенного типа (класса ), а классы образуют иерархию с

наследованием свойств .

Взаимодействие программных объектов в такой системе осуществляется путем передачи сообщений .

П р и м е ч а н и е. Такое представление программы впервые было использовано в языке имитационного моделирования сложных систем Simula, появившемся еще в 60-х годах.

Естественный для языков моделирования способ представления программы получил развитие в другом специализированном языке моделирования - языке Smalltalk (70-е годы), а затем был

Страница 2 из 51

Основные принципы ООП

использован в новых версиях универсальных языков программирования, таких как Pascal, С++,

Основное достоинство ООП - сокращение количества межмодульных вызовов и уменьшение объемов информации, передаваемой между модулями,

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

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

(объектов) программы.

Кроме этого, объектный подход предлагает новые технологические средства разработки, такие как наследование, полиморфизм, композиция, наполнение ,

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

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

Основной недостаток ООП - некоторое снижение быстродействия за счет более сложной организации программной системы.

В основу ООП положены следующие п р и н ц и п ы : абстрагирование,

ограничение доступа, модульность, иерархичность, типизация, параллелизм,

устойчивость.

Рассмотрим, что представляет собой каждый принцип.

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

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

третьем - материалы, из которых он сделан, в четвертом - закон движения

Страница 3 из 51

Основные принципы ООП

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

так и определяющих его поведение) в единую программную единицу некий

абстрактный тип (класс).

О г р а н и ч е н и е д о с т у п а - сокрытие отдельных элементов реализации абстракции, не затрагивающих существенных характеристик ее как целого.

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

интерфейс - совокупность доступных извне элементов реализации абстракции (основные характеристики состояния и поведения);

реализация - совокупность недоступных извне элементов реализации абстракции (внутренняя организация абстракции и механизмы реализации ее поведения).

Ограничение доступа в ООП позволяет разработчику:

выполнять конструирование системы поэтапно, не отвлекаясь на особенности реализации используемых абстракций;

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

Сочетание объединения всех свойств предмета (составляющих его состояния и поведения) в единую абстракцию и ограничения доступа к реализации этих свойств получило название инкапсуляции.

М о д у л ь н о с т ь - принцип разработки программной системы,

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

Страница 4 из 51

Основные принципы ООП

модульного программирования, следование ему упрощает проектирование и

отладку программы.

И е р а р х и я - ранжированная или упорядоченная система абстракций.

Принцип иерархичности предполагает использование иерархий при разработке программных систем.

В ООП используются два вида иерархии.

Иерархия «целое/часть» - показывает, что некоторые абстракции включены

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

Иерархия «общее/частное» - показывает, что некоторая абстракция является частным случаем другой абстракции, например, « обеденный стол -

конкретный вид стола», а « столы - конкретный вид мебели». Используется при

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

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

наследование).

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

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

объявляется тип, который определяет множество операций над

Страница 5 из 51

Основные принципы ООП

соответствующим программным объектом. Рассматриваемые далее языки программирования на основе Паскаля используют строгую, а на основе С -

среднюю степень типизации.

Использование принципа типизации обеспечивает:

раннее обнаружение ошибок, связанных с недопустимыми операциями над программными объектами (ошибки обнаруживаются на этапе компиляции программы при проверке допустимости выполнения данной операции над программным объектом);

упрощение документирования;

возможность генерации более эффективного кода.

Тип может связываться с программным объектом статически (тип объекта определен на этапе компиляции - раннее связывание) и динамически (тип объекта определяется только во время выполнения программы - позднее связывание). Реализация позднего связывания в языке программирования позволяет создавать переменные - указатели на объекты, принадлежащие различным классам (полиморфные объекты), что существенно расширяет возможности языка.

П а р а л л е л и з м - свойство нескольких абстракций одновременно находиться в активном состоянии, т.е. выполнять некоторые операции.

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

например, относятся задачи автоматического управления несколькими процессами.

Реальный параллелизм достигается только при реализации задач такого типа на многопроцессорных системах, когда имеется возможность выполнения каждого процесса отдельным процессором. Системы с одним процессором имитируют параллелизм за счет разделения времени процессора между задачами управления различными процессами. В зависимости от типа используемой операционной системы (одноили мультипрограммной)

Страница 6 из 51

Основные принципы ООП

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

MS DOS), либо используемой ОС (как в системах Windows).

У с т о й ч и в о с т ь - свойство абстракции существовать во времени независимо от процесса, породившего данный программный объект, и/или в пространстве, перемещаясь из адресного пространства, в котором он был создан.

Различают:

∙ временные объекты, хранящие промежуточные результаты некоторых действий, например вычислений;

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

∙ глобальные объекты, существующие пока программа загружена в память;

∙ сохраняемые объекты, данные которых хранятся в файлах внешней памяти между сеансами работы программы.

Все указанные выше принципы в той или иной степени реализованы в различных версиях объектно-ориентированных языков.

Объектно-ориентированные языки программирования. Язык считается объектно-ориентированным, если в нем реализованы первые четыре из рассмотренных семи принципов.

Особое место занимают объектные модели Delphi и C++Builder. Эти модели обобщают опыт ООП для MS DOS и включают некоторые новые средства,

обеспечивающие эффективное создание более сложных систем. На базе этих моделей созданы визуальные среды для разработки приложений Windows.

Сложность программирования под Windows удалось существенно

снизить за счет создания специальных библиотек объектов, « спрятавших» многие элементы техники программирования.

Страница 7 из 51

Основные принципы ООП

Этапы разработки программных систем с использованием ООП.

Процесс разработки программного обеспечения с использованием ООП включает четыре этапа: анализ, проектирование, эволюция, модификация.

Рассмотрим эти этапы.

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

Проект ирование . Различают :

логическое проектирование, при котором принимаемые решения практически не зависят от условий эксплуатации (операционной системы и используемого оборудования);

физическое проектирование, при котором приходится принимать во внимание указанные факторы.

Логическое проектирование заключается в разработке структуры классов:

определяются поля для хранения составляющих состояния объектов и алгоритмы методов, реализующих аспекты поведения объектов. При этом используются рассмотренные выше приемы разработки классов (наследование,

композиция, наполнение, полиморфизм и т.д.). Результатом является иерархия или диаграмма классов, отражающие взаимосвязь классов, и описание классов.

Физическое проектирование включает объединение описаний классов в модули, выбор схемы их подключения (статическая или динамическая компоновка), определение способов взаимодействия с оборудованием, с

операционной системой и/или другим программным обеспечением (например,

базами данных, сетевыми программами), обеспечение синхронизации процессов для систем параллельной обработки и т.д.

Страница 8 из 51

Основные принципы ООП

Э в о л ю ц и я с и с т е м ы. Это процесс поэтапной реализации и

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

работающий прототип будущей системы. Он тестируется и отлаживается.

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

Полученный вариант также тестируется и отлаживается, и так далее, до реализации всех возможностей системы.

Использование поэтапной реализации существенно упрощает тестирование и отладку программного продукта.

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

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

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

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

Страница 9 из 51

Основные принципы ООП

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

Обычно проектирование начинается, когда какой-либо фрагмент предметной области достаточно полно описан в процессе анализа.

Рассмотрение основных приемов объектного подхода начнем с объектной декомпозиции.

Объектная декомпозиция

Как уже упоминалось выше, при использовании технологии ООП решение представляется в виде результата взаимодействия отдельных функциональных элементов некоторой системы, имитирующей процессы,

происходящие в предметной области поставленной задачи.

В такой системе каждый функциональный элемент, получив некоторое входное воздействие (называемое сообщением) в процессе решения задачи,

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

Функциональные элементы системы, параметры и поведение которой определяются условием задачи, обладающие самостоятельным поведением

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

Процесс представления предметной области задачи в виде совокупности объектов, обменивающихся сообщениями, называется объектной декомпозицией.

Страница 10 из 51

Основные принципы ООП

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

Пример. Объектная декомпозиция (имитационная модель

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

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

Процесс работы бензоколонки с тремя заправочными местами можно представить в виде диаграммы.

Общая информация

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

Основные принципы ООП

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

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

Полиморфизм

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

Языки ООП

Принципы ООП используются в таких наиболее популярных языках программирования, как C++ и Java, на которых разработана значительная часть программ и приложений. Есть и менее используемые языки ООП - это Delphi, Object Pascal, Ruby и многие другие.

Критика ООП

Несмотря на в основном позитивные высказывания в сторону данной методологии, нередко принципы ООП подвергаются и критике. Как и у у ООП есть свои недостатки.

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

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

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

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

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

История

Основатели ООП - выдающиеся норвежские ученые Кристен Нигаард (Cristen Nygaard) и Оле-Йохан Даль (Ole-Johan Dahl). Работая над моделированием судовождения, Нигаард понял, что существующие программные средства малоэффективны в создании столь сложных программ, и тогда Нигаард начал разрабатывать концепции нового программирования, позволяющего преодолеть барьеры сложности, и которое впоследствии было названо объектно-ориентированным (сам термин был придуман Аланом Кеем, автором языка Java). Вместе с Оле-Йоханом Далем Нигаард разработал основные положения ООП и практические механизмы их реализации, которые затем были воплощены в первом ООЯ Симула (Simula). Заслуги этих ученых были по достоинству оценены мировым научным сообществом, и в 2001 году Нигаард и Даль стали лауреатами премии имени Алана Тьюринга - своеобразного аналога Нобелевской премии в области computer science.

Язык Симула пользовался известностью в академических кругах, однако по ряду причин не завоевал популярности среди разработчиков коммерческого ПО. Тем не менее основные идеи и возможности ООП были очень привлекательны для программистов. Впоследствии были созданы другие ООЯ: SmallTalk (1980), C++ (1985), Eiffel (1986), Object Pascal (1986) и Delphi (1995), Oberon-2 (1991), Java (1991), Visual Basic (1991) и многие другие. Некоторые из этих языков стали промышленными стандартами в программировании.

Особенности ООП

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

В обыденной жизни люди используют (пусть даже неосознанно) различные приемы “экономии мышления”, позволяющие осмысливать и выражать сложные явления в простых понятиях. Типичными приемами “экономии мышления” являются:

· абстрагирование (отбрасывание несущественных деталей);

· обобщение (выделение общих существенных признаков у разных явлений или предметов);

· классификация (осознание связи между явлениями и степени их схожести).

Эти простые приемы помогают человеку справиться со сложностью рассматриваемых явлений. И объектно-ориентированные языки программирования также должны предоставлять подобные средства для “борьбы со сложностью” программ. Для реализации объектно-ориентированного подхода в языки программирования вводятся новые понятия:

· объекты - особые программные структуры, объединяющие данные и алгоритмы их обработки;

· инкапсуляция - сокрытие подробностей функционирования объектов;

· наследование - “сокращенный” способ создания новых классов;

· полиморфизм - возможность применения нескольких реализаций одной функции.

Объекты и классы

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

Классы - это объектные типы данных. Подобно тому, как целые числа принадлежат какому-нибудь целочисленному типу (например, integer или byte), объекты также принадлежат какому-либо объектному типу - классу. Все объекты одного класса имеют одинаковый набор полей и одинаковый набор методов.

В некоторых языках (C++, Java) объекты называются экземплярами класса (instances).

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

Инкапсуляция

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

С позиций “борьбы со сложностью” инкапсуляция позволяет переложить часть контроля за правильностью работы с объектами на компилятор (компьютер).

Различные ООЯ предлагают разные возможности по инкапсуляции полей и методов (от полного отсутствия и до автоматического сокрытия всех полей). В промышленных ООЯ, таких, как C++, Java, Delphi, Eiffel и т.д., предусмотрены три уровня инкапсуляции полей и методов:

· public - на обращение к публичным полям и методам объектов нет никаких ограничений;

· protected - прямое обращение к защищенным полям и методам возможно только из методов данного класса и методов дочерних классов;

· private - прямое обращение к приватным полям и методам возможно исключительно из методов данного класса.

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

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

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

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

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

Совокупность всех классов-предков и классов-потомков называется иерархией классов .

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

Полиморфизм

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

Если провести аналогию с реальной жизнью, то полиморфизм соответствует обобщенным действиям. Например, глагол “музицировать” означает “играть на музыкальном инструменте”. Но на разных музыкальных инструментах играют по-разному. Термин один, а вариантов действия - много. Значит, “музицировать” - полиморфное действие . В ООП действию “музицировать” соответствовал бы полиморфный метод , имеющий свои реализации для каждого класса музыкальных инструментов.

В ООП есть два вида полиморфных методов - перегруженные и виртуальные .

Перегруженные методы предназначены для выполнения с данными разных типов. Они имеют одинаковые имена, но разные списки аргументов и/или тип возвращаемого значения.

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

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

Достоинства виртуальных методов проявляются только при использовании иерархии классов. Типичная схема использования виртуальных методов такова:

· В классе-предке иерархии объявляется полиморфный метод, который описывает некое полезное действие. При этом либо он использует виртуальный метод, либо сам является виртуальным.

· В классах-потомках соответствующий виртуальный метод переопределяется - для каждого класса-потомка это полезное действие выполняется по-своему.

· При вызове для объекта, принадлежащего классу-потомку, полиморфного метода на деле используется виртуальный метод класса-потомка (а не класса-предка).

Яркий пример подобного использования виртуальных методов - система графического оконного интерфейса Delphi или Visual Basic: каждый видимый элемент графического интерфейса - кнопка, ползунок, окно и т.п. - должен быть потомком класса TControl. В классе TControl вводятся общие полиморфные методы отрисовки элементов графического интерфейса, а любой его потомок может нарисовать себя на экране своим собственным способом.

Объектно-ориентированное программирование явно не упоминается в Стандарте 2004 года, хотя в Обязательном минимуме содержания образования по информатике для учащихся профильных заведений (Уровень Б) такая тема присутствовала: Объектно-ориентированное программирование: объект, свойства объекта, операции над объектом . Там же упоминалась и объектно-ориентированная технология программирования.

Тем не менее ООП не просто вошло в практику преподавания информатики (программирования) многих школ, но и присутствует на страницах школьных учебников (Угринович Н.Д. Информатика и информационные технологии. Учебник для 10–11-х классов, 2005. М.: БИНОМ. Лаборатория Знаний). Кроме того, в пропедевтическом курсе информатики для начальной школы (рабочие тетради авторского коллектива под руководством А.Горячева. 1–4-е классы) также вводятся понятия объекта и его свойств .

Технология (парадигма) ООП требует не столько освоения современной техники программирования, сколько умения разрабатывать объектную модель решаемой задачи. Для этого требуется хорошо знать базовые принципы ООП и программирования вообще, однако знание какого-либо ООЯ не является обязательным - об этом неоднократно писали основоположники и теоретики ООП. Так, Гради Буч в своей книге “Объектно-ориентированное проектирование и анализ” высказывает следующую максиму: “Писать программы в объектно-ориентированном стиле можно в любом (не объектно-ориентированном) языке программирования”. Для построения алгоритма по технологии ООП требуется сформировать список объектов, с которыми работает алгоритм, продумать свойства каждого объекта и реализовать алгоритм как взаимодействие описанных объектов.

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

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

Не отрицая полностью предложение некоторых преподавателей информатики поставить объектно-ориентированный подход во главу угла изучения программирования, в том числе в школе, отметим, что ООП невозможно без таких базовых понятий, как программа, исполнитель, переменная, условие, цикл и т.д. Концепция ООП также включает в себя классическое процедурное программирование (см. “Подпрограммы ”), как механика Эйнштейна - механику Ньютона: достаточно представить себе процедурную программу как единственный объект с опущенным для простоты именем. Поэтому в первую очередь задача курса программирования в школе - научить базовым вещам. И лишь при возможности работы с современными визуальными средами программирования (Delphi, Visual Basic, Visual C++
и т.п.) познакомить с понятием объектов и их использованием в основном с помощью методики обучения программированию “по образцу”.