Войти
Android, Windows, Apple, Ликбез. Социальные сети. Драйверы
  • Японские телефоны Новый японский смартфон
  • Lenovo G500S: характеристики, основные особенности
  • Определяем серию продукта видеокарт Nvidia Характеристики карты nvidia 9800 gt
  • А конкуренты у смартфона есть
  • Что такое расширение файла TRZ?
  • Не работает динамик в "айфоне"
  • ООП - это что такое? Основные принципы объектно-ориентированного программирования. Принципы ооп В объектно ориентированном программировании полиморфизм характеризуется

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

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

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

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

    Примером объектно-ориентированных языков являются: Object Pascal , C++, Java .

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

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

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

    Объект состоит из следующих трех частей:

    Имя объекта;

    Состояние (переменные состояния);

    Методы (операции).

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

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


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

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

    Практический подход. В современных объектно-ориентированных языках программирования (php, Java, C++, Oberon, Python, Ruby, Smalltalk, Object Pascal) создание класса сводится к написанию некоторой структуры, содержащей набор полей и методов. Практически класс может пониматься как некий шаблон, по которому создаются объекты — экземпляры данного класса. Экземпляры одного класса созданы по одному шаблону, поэтому имеют один и тот же набор полей и методов.

    Отношения между классами:

    Наследование (Генерализация) — объекты дочернего класса наследуют все свойства родительского класса.

    Ассоциация — объекты классов вступают во взаимодействие между собой.

    Агрегация — объекты одного класса входят в объекты другого.

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

    Класс-Метакласс — отношение, при котором экземплярами одного класса являются другие классы.

    Виды классов:

    Базовый (родительский) класс;

    Производный класс (наследник, потомок);

    Абстрактный класс;

    Виртуальный класс;

    Интерфейс.

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

    Методы - инкапсулированные в классе процедуры и функции, то есть способы работы с данными.

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

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

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

    Цели инкапсуляции:

    § предельная локализация изменений при необходимости таких изменений,

    § прогнозируемость изменений (какие изменения в коде надо сделать для заданного изменения функциональности) и прогнозируемость последствий изменений.

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

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

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

    Суть инкапсуляции : Переменные состояния объекта скрыты от внешнего мира. Изменение состояния объекта (его переменных) возможно ТОЛЬКО с помощью его методов (операций). Почему это так важно? Этот принцип позволяет защитить переменные состояния объекта от неправильного их использования.

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

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

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

    Наследование - представляет собой возможность построения иерархии объек-тов с использованием наследования их характеристик.

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

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

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

    Для реализации таких иерархий в языке программирования предусмотрен полиморфизм. Слово полиморфизм имеет греческое происхождение и переводится как «имеющий много форм».

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Полиморфизм

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

    Языки ООП

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

    Критика ООП

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

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

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

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

    Я не умею программировать на объектно-ориентированных языках. Не научился. После 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… ну, вы поняли. В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект - для хранения результата. В ООП мне приходится засорять память постоянно.

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

    Из своего опыта могу сказать, что всегда считал что понимал ООП, что же тут такого то - полиморфизм, инкапсуляция и наследование, но вот когда дошло до дела, то туговато пришлось. Хочу разложить всё по полочкам чтобы никто не наступил на мои грабли в будущем:)

    Шаг 1.

    Немного теории:

    Объектно-ориентированное программирование (в дальнейшем ООП) - парадигма программирования, в которой основными концепциями являются понятия объектов и классов.

    В центре ООП находится понятие объекта.

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

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

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

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

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

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

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

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

    Шаг 2.

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

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

    Зачем же это нужно?

    Думаю, вам бы не хотелось, чтобы кто-то, что-то изменял в написанной вами библиотеки.

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

    Цель инкапсуляции – уйти от зависимости внешнего интерфейса класса (то, что могут использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло за собой изменение внешнего поведения класса. Давайте рассмотрим, как ею пользоваться.

    Существует 4 вида модификаторов доступа: public , protected , private и default .

    Public – уровень предполагает доступ к компоненту с этим модификатором из экземпляра любого класса и любого пакета.

    Protected – уровень предполагает доступ к компоненту с этим модификатором из экземпляров родного класса и классов-потомков, независимо от того, в каком пакете они находятся.

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

    Private – уровень предполагает доступ к компоненту с этим модификатором только из этого класса.

    Public class Human { public String name; protected String surname; private int age; int birthdayYear; }

    public String name; - имя, которое доступное из любого места в приложении.
    protected String surname; - фамилия доступна из родного класса и потомков.
    private int age; - возраст доступен только в рамках класса Human.
    int birthdayYear; - хоть не указывается явный модификатор доступа, система понимает его как default, год рождения будет доступен всему пакету, в котором находится класс Human.

    Для разных структурных элементов класса предусмотрена возможность применять только определенные уровни модификаторов доступа.

    Для класса - только public и default.

    Для атрибутов класса - все 4 вида.

    Для конструкторов - все 4 вида.

    Для методов - все 4 вида.

    Шаг 3.

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

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

    Наследование является важным, поскольку оно позволяет поддерживать концепцию иерархии классов (hierarchical classification). Применение иерархии классов делает управляемыми большие потоки информации.

    Разберем этот механизм на классическом примере: Геометрические фигуры.

    У нас есть интерфейс Figure:

    Public interface Figure { public void draw (); public void erase (); public void move (); public String getColor (); public boolean setColor (); }

    Интерфейс (более детально будут рассмотрены в скором будущем ) - нам говорит, как должен выглядеть класс, какие методы в себе содержать, какими переменными и типами данных манипулировать. Сам интерфейс не реализует методы, а создает как бы скелет для класса, который будет расширять этот интерфейс. Есть класс Figure, который расширяет интерфейс Figure:

    Public class Figure implements сайт.oop.inheritance.interfaces.Figure{ @Override public void draw() { //need to implement } @Override public void erase() { //need to implement } @Override public void move(int pixel) { //need to implement } @Override public String getColor() { return null; } @Override public boolean setColor(String colour) { return false; } }

    В этом классе мы реализуем все методы интерфейса Figure .

    public class Figure implements сайт.oop.inheritance.interfaces.Figure - с помощью ключевого слова implements мы перенимаем методы интерфейса Figure для реализации.

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

    И соответственно у нас есть 3 класса самих фигур, которые наследуются от класса Figure. Класс Figure является родительским классом или классом-родителем, а классы Circle, Rectungle и Triangle - являются дочерними.

    Public class Circle extends Figure { @Override public void draw() { super.draw(); } @Override public void erase() { super.erase(); } @Override public void move(int pixel) { super.move(pixel); } @Override public String getColor() { return super.getColor(); } @Override public boolean setColor(String colour) { return super..oop.inheritance.Figure{ @Override public void draw() { super.draw(); } @Override public void erase() { super.erase(); } @Override public void move(int pixel) { super.move(pixel); } @Override public String getColor() { return super.getColor(); } @Override public boolean setColor(String colour) { return super..oop.inheritance.Figure{ @Override public void draw() { super.draw(); } @Override public void erase() { super.erase(); } @Override public void move(int pixel) { super.move(pixel); } @Override public String getColor() { return super.getColor(); } @Override public boolean setColor(String colour) { return super.setColor(colour); } }

    public class Triangle extends сайт.oop.inheritance.Figure - это значит, что класс Triangle наследует класс Figure .

    super.setColor(colour); - super модификатор, позволяющий вызывать методы из класса родителя.

    Теперь каждый класс перенял свойства класса Figure. Что собственно это нам дало?

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

    Наверное возник вопрос: чем же extends отличается от implements ?

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

    В дочерних классах мы можем спокойно добавлять новые интересующие нас методы. Например, мы хотим добавить в класс Triangle 2-а новых метода: flimHorizontal () и flipVertical ():

    /** * New Method */ public void flipVertical () { }; /** * New Method */ public void flipHorizontal () { };

    Теперь эти 2-а метода принадлежат сугубо классу Triangle . Этот подход используется когда базовый класс не может решить всех проблем.

    Или можно использовать другой подход, изменить или переписать методы в дочерним классе:

    Довольно интересный факт: в java запрещено множественное наследование, но любой из классов по умолчанию наследуется то класса Object . То есть при наследовании любого класса у нас получается множественное наследование)

    Но не стоит забивать этим голову!

    Шаг 4.

    Полиморфизм.

    В более общем смысле, концепцией полиморфизма является идея “один интерфейс, множество методов “.

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

    Вам, как программисту, не нужно делать этот выбор самому. Нужно только помнить и использовать общий интерфейс.

    Public class Parent { int a = 2; } public class Child extends Parent { int a = 3; }

    Прежде всего, нужно сказать, что такое объявление корректно.

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

    Компилятор может опираться только на тип ссылки, с помощью которой происходит обращение к полю:

    Child c = new Child(); System.out.println(c.a); // результатом будет 3 Parent p = c; System.out.println(p.a); //результатом будет 2

    Данное объявление так и называется – «скрывающим ». Родительское поле продолжает существовать.

    К нему можно обратиться явно:

    Class Child extends Parent { int a = 3; //скрывающее объявление int b = ((Parent)this).a; //громоздкое обращение к родительскому полю int c = super.a; //простое обращение к родительскому полю }

    Переменные b и c получат значения, родительского поля a . Хотя выражение с super более простое, оно не позволит обратиться на два уровня вверх по дереву наследования.

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

    К нему можно обратиться явным приведением, как это делается для b .

    Class Parent { int x = 0; public void printX() { System.out.println(x); } } class Child extends Parent { int x = -1; }

    Каков будет результат для new Child.printX() ; ?

    Метод вызывается с помощью ссылки типа Child , но метод определен в классеParent и компилятор расценивает обращение к полю x в этом методе именно как к полю класса Parent . Результатом будет 0 .

    Рассмотрим случай переопределения методов:

    Class Parent { public int getValue() { return 0; } } class Child extends Parent { public int getValue() { return 1; } } Child c = new Child(); System.out.println(c.getValue()); // результатом будет 1 Parent p = c; System.out.println(p.getValue()); // результатом будет 1

    Родительский метод полностью перекрыт.

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

    Хотя старый метод снаружи недоступен, внутри класса-наследника к нему можно обратиться с помощью super .

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

    Шаг 5.

    Абстракция:

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

    В контексте ООП абстракция - это обобщение данных и поведения для типа, находящегося выше текущего класса по иерархии.

    Перемещая переменные или методы из подкласса в супер класс, вы обобщаете их. Это общие понятия, и они применимы в языке Java. Но язык добавляет также понятия абстрактных классов и абстрактных методов .

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

    Например, вы можете создать класс Animal (животное). Нет смысла создавать экземпляр этого класса: на практике вам нужно будет создавать экземпляры конкретных классов , например, Dog (собака). Но все классы Animal имеют некоторые общие вещи, например, способность издавать звуки. То, что Animal может издавать звуки, еще ни о чем не говорит.

    Издаваемый звук зависит от вида животного.

    Как это смоделировать?

    Определить общее поведение в абстрактном классе и заставить подклассы реализовывать конкретное поведение, зависящее от их типа.

    В иерархии могут одновременно находиться как абстрактные, так и конкретные классы.

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

    Наш класс Person содержит некоторый метод поведения, и мы пока не знаем, что он нам необходим. Удалим его и заставим подклассы реализовывать это поведение полиморфным способом. Мы можем сделать это, определив методы Person как абстрактные. Тогда наши подклассы должны будут реализовывать эти методы.

    Public abstract class Person { abstract void move(); abstract void talk(); } public class Adult extends Person { public Adult() { } public void move() { System.out.println("Walked."); } public void talk() { System.out.println("Spoke."); } } public class Baby extends Person { public Baby() { } public void move() { System.out.println("Crawled."); } public void talk() { System.out.println("Gurgled."); } }

    Что мы сделали в приведенном выше коде?

    Мы изменили Person и указали методы как abstract , заставив подклассы реализовывать их.
    Мы сделали Adult подклассом Person и реализовали эти методы.
    Мы сделали Baby подклассом Person и реализовали эти методы.

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

    Теперь, поскольку Adult и Baby являются подклассами Person , мы можем обратиться к экземпляру каждого класса как к типу Person.

    Абстрактные типы данных

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

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

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

    Абстракция данных предполагает определение и рассмот­рение абстрактных типовданных (АТД) или, что то же самое, новых типов данных, введенных пользователем.

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

    Понятие объектно-ориентированного программирования

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

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

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

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

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

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

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

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

    Базовыми блоками объектно-ориентированной програм­мы являются объекты и классы. Содержательно объект мож­но представить как что-то ощущаемое или воображаемое и имеющее хорошо определенное поведение. Таким образом, объект можно либо увидеть, либо потрогать, либо, по край­ней мере, знать, что он есть, например, представлен в виде информации, хранимой в памяти компьютера. Дадим определение объекта, придерживаясь мнения Гради Буча: «Объект – осязаемая сущность, которая четко проявляет свое поведение».

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

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

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

    Определим теперь понятия состояния, поведения и иденти­фикации объекта.

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

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

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

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

    Базовые принципы ООП

    К базовым принципам объектно-ориентированного стиля программирования относятся:

    • пакетирование или инкапсуляция;
    • наследование;
    • полиморфизм;
    • передача сообщений.

    Пакетирование (инкапсуляция)

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

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

    В рамках ООП данные называются полями объекта, а алгоритмы – объектными методами.

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

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

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

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

    По Гради Бучу «наследование – это такое отношение между объектами, когда один объект повторяет структуру и поведение другого».

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

    Типы верхних уровней объектно-ориентированной иерархии, как правило, не имеют конкретных экземпляров объектов. Не существует, например, конкретного живого организма, который бы сам по себе назывался «млекопитающее» или «птица». Такие типы называют абстрактными. Конкретные экземпляры объектов имеют, как правило, типы самых нижних уровней ОО-иерархии: «крокодил Гена» – конкретный экземпляр объекта типа «крокодил», «кот Матроскин» – конкретный экземпляр объекта типа «кошка».

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

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

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

    Когда вы строите новый класс, наследуя его из сущест­вующего класса, можно:

    • добавить в новый класс новые компоненты-данные;
    • добавить в новый класс новые компоненты-функции;
    • заменить в новом классе наследуемые из старого класса компоненты-функции.

    Полиморфизм

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

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

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

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

    Таким образом, в нашем примере с объектами-животными действие «бежать» будет называться полиморфическим действием, а многообразие форм проявления этого действия – полиморфизмом.

    Описание объектного типа

    Класс или объект – это структура данных, которая содержит поля и методы. Как всякая структура данных она начинается зарезервированным словом и закрывается оператором end . Формальный синтаксис не сложен: описание объектного типа получается, если в описании записи заменить слово record на слово object или class и добавить объявление функций и процедур над полями.

    Type <имя типа объекта>= object
    <поле>;
    <поле>;
    ….
    <метод>;
    <метод>;
    end ;

    В ObjectPascal существует специальное зарезервированное слово class для описания объектов, заимствованное из С++.

    Type <имя типа объекта>= class
    <поле>;
    ….
    <метод>;
    <метод>;
    end ;

    ObjectPascal поддерживает обе модели описания объектов.

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

    Пример . Вводится объектный тип «предок», который имеет поле данных Name (имя) и может выполнять два действия:

    • провозглашать: «Я – предок!»;
    • сообщать свое имя.

    Type tPredoc = object Name: string ; {поле данных объекта}
    Procedure Declaration ; {объявление методов объекта}
    Procedure MyName ;
    End ;

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

    Procedure tPredoc.Declaration ; {реализация метода объекта}
    begin
    writeln ("Я – предок!");
    end ;
    Procedure tPredoc.MyName ; {реализация метода объекта}
    begin
    writeln("Я –", Name);
    end;

    Внутри описания методов на поля и методы данного типа ссылаются просто по имени. Так метод MyName использует поле Name без явного указания его принадлежности объекту так, если бы выполнялся неявный оператор with <переменная_типа_объект> do .

    Под объектами понимают и переменные объектного типа – их называют экземплярами . Как всякая переменная, экземпляр имеет имя и тип: их надо объявить.

    …….{объявление объектного типа и описание его методов}
    var v 1: tPredoc ; {объявление экземпляра объекта}
    begin
    v1. Name:= "Петров Николай Иванович";
    v1.Declaration;
    v1.MyName
    end.

    Использование поля данных объекта v1 не отличается по своему синтаксису от использования полей записей. Вызов методов экземпляра объекта означает, что указанный метод вызывается с данными объекта v 1. В результате на экран будут выведены строчки

    Я – предок!
    Я – Петров Николай Иванович

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

    Например, в тексте программы вместо операторов

    возможно использование оператора with такого вида

    with v1 do
    begin
    Name:= "Петров Николай Иванович";
    Declaration ;
    MyName
    End ;

    Более того, применение оператора with с объектными типами, также как и для записей не только возможно, но и рекомендуется.

    Иерархия типов (наследование)

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

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

    ООП всегда начинается с базового класса. Это шаблон для базового объекта. Следующим этапом является определение нового класса, который называется производным и является расширением базового.

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

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

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

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

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

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

    Правило 2 . Доступ к полям и методам родительских типов в рамках описания любых дочерних типов выполняется так, как будто-бы они описаны в самом дочернем типе.

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

    Правило 4 . Дочерний тип может доопределить произвольное число собственных методов и информационных полей.

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

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

    Продолжим рассмотрение нашего примера. В дополнение к введенному нами типу предка tPredoc можно ввести типы потомков:

    tуре tSon= оbject(tPredoc) {Тип, наследующий tPredoc }
    procedure Declaration; {перекрытие методов предка}
    procedure Му Name(Predoc: tPredoc);
    end ;

    Tуре tGrandSon=object(tSon) {Тип, наследующий tSon}
    procedure Declaration ; {перекрытие методов предка}
    end ;

    Имя типа предка приводится в скобках после слова оbject. Мы породили наследственную иерархию из трех типов: tSon («сын») наследник типу tPredoc , а тип tGrandSon (“внук”) ­- типу tSon. Тип tSon переопределяет методы Declaration и Му N а m е, но наследует поле Name . Тип tGrandSon переопределяет только метод Declaration и наследует от общего предка поле Name , а от своего непосредственного предка (типа tSon) переопределенный метод Declaration .

    Давайте разберемся, что именно мы хотим изменить в родительских методах. Дело в том, что «сын» должен провозглашать несколько иначе, чем его предок, а именно сообщить "Я – отец!"

    procedure tSon.Declaration ; {реализация методов объектов - потомков}
    begin
    writeln (" Я - отец!");
    end;

    А называя свое имя, “сын” должен сообщить следующие сведения:

    • Я <фамилия имя отчество >
    • Я – сын <фамилия имя отчество своего предка>

    procedure tSon .Му Name (predoc: tPredoc);
    begin
    inherited Му Name ; {вызов метода непосредственного предка}
    writeln ("Я - сын ", predoc.Name, " а ");
    end;

    В нашем примере потомок tSon из метода Му Name вызывает одноимен­ный метод непосредственного предка типа tPredoc . Такой вызов обес­печивается директивой inherited , после которой указан вызываемый метод непосредственного предка. Если возникает необходимость вызвать метод отдаленного предка в каком-нибудь дочернем типе на любом уровне иерархии, то это можно сделать с помощью уточненного идентификатора, т.е. указать явно имя типа родительского объекта и через точку – имя его метода:

    Теперь давайте разберемся с «внуком». Метод, в котором «внук» называет свое имя, в точности такой же, как и у его непосредственного предка (типа tSon), поэтому нет необходимости этот метод переопределять, этот метод лучше автоматически наследовать и пользоваться им как своим собственным. А вот в методе Declaration нужно провозгласить "Я – внук!", поэтому метод придется переопределить.

    procedure tGrandSon.Declaration;
    begin
    writeln (" Я - внук!");
    end;

    Рассмотрим пример программы, в которой определим экземпляр типа tPredoc , назовем его «дед», экземпляр типа tSon – «отец», и экземпляр типа tGrandSon – «внук». Потребуем от них, чтобы они представились.

    Пример программы с испльзованием ООП

    {заголовок программы}
    ……………….
    {раздел описания типов, в том числе и объектных типов tPredoc , tSon , tGrandSon }
    {Обратите внимание! Экземпляры объектных типов можно описать как типизированные константы, что мы для примера и сделали ниже}
    const ded: tPredoc = (Name: "Петров Николай Иванович");
    otec: tSon = (Name: "Петров Сергей Николаевич");
    vnuk: tGrandSon = (Name: "Петров Олег Сергеевич");
    {раздел описания процедур и функций, где обязательно должны быть написаны все объявленные в объектных типах методы}
    begin
    ded.Declaration ; {вызов методов общего предка}
    ded.Му Name;
    writeln;
    otec.Declaration;
    otec.MyName(ded); { вызов методов объекта otec типа tSon}
    writeln;
    vnuk.Declaration; { вызов методов объекта vnuk типа tGrandSon}
    vnuk.MyName (otec);
    end .

    Наша программа выведет на экран:

    Пример вывода на экран результата

    Я -предок!
    Я -Петров Николай Иванович

    Я -отец!
    Я -Петров Сергей Николаевич
    Я -сын Петров Николай Ивановича

    Я -внук!
    Я -Петров Олег Сергеевич
    Я -сын Петров Сергей Николаевича

    Обратите внимание, что в заголовке процедуры tSon . MyName в качестве параметра приведен тип данных tPredoc , а при использовании этой процедуры ей передаются переменные как типа tPredoc , так и типа tSon . Это возможно, так как пре­док совместим по типу со своими потомками. Обратное несправедливо. Если мы заменим в заголовке процедуры tSon . MyName при описании параметров тип tPredoc на tSon , компилятор укажет на несовместимость типов при использовании перемен­ной ded в строке otec . MyName (ded).

    Полиморфизм и виртуальные методы

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

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

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

    В рассмотренном выше примере во всех трех объектных типах tPredoc , tSon и tGrandSon действуют одноименные методы Declaration и MyName . Но в объектном типе tSon метод MyName выполняется несколько иначе, чем у его предка. А все три одноименных метода Declaration для каждого объекта выполняются по-своему.

    Методы объектов бывают статическими, виртуальными и динамическими.

    Статические методы

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

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

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

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

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

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

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

    procedure Method (список параметров); virtual;

    Использование виртуальных методов в иерархии типов объектов имеет определенные ограничения:

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

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

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

    Конструктор – это специальный метод, который инициализирует объект, содержащий виртуальные методы. Заголовок конструктора выглядит так:

    constructor Method (список параметров);

    Зарезервированное слово constructor заменяет слова procedure и virtual .

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

    У каждого объекта своя таблица виртуальных методов VMT . Именно это и позволяет одноименному методу вызывать различные процедуры.

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

    Заголовок деструктора выглядит таким образом:

    destructor Done ;

    Основное назначение деструкторов – уничтожение VMT данного объекта. Часто деструктор не выполняет других действий и представляет собой пустую процедуру.

    destructor Done ;
    begin end ;