Основные механизмы объектно ориентированного программирования. Принципы объектно-ориентированного программирования. Что такое ООП
Объе́ктно-ориенти́рованное программи́рование (ООП) - методология программирования , основанная на представлении программы в виде совокупности объектов , каждый из которых является экземпляром определённого класса , а классы образуют иерархию наследования .
Идеологически ООП - подход к программированию как к моделированию информационных объектов, решающий на новом уровне основную задачу структурного программирования : структурирование информации с точки зрения управляемости , что существенно улучшает управляемость самим процессом моделирования, что, в свою очередь, особенно важно при реализации крупных проектов.
Управляемость для иерархических систем предполагает минимизацию избыточности данных (аналогичную нормализации) и их целостность, поэтому созданное удобно управляемым - будет и удобно пониматься. Таким образом, через тактическую задачу управляемости решается стратегическая задача - транслировать понимание задачи программистом в наиболее удобную для дальнейшего использования форму.
Наиболее заметные отличия в проявлении показателей качества между языками разных видов:
- В мейнстримных языках декларируемые принципы нацелены на повышение изначально низкого для императивного программирования коэффициента повторного использования кода . В полиморфно типизированных применение концепций ООП, напротив, означает очевидное его снижение из-за перехода от параметрического полиморфизма к ad-hoc-полиморфизму . В динамически типизированных языках (Smalltalk , Python , Ruby) эти принципы используются для логической организации программы, и их влияние на коэффициент повторного использования трудно спрогнозировать - он сильно зависит от дисциплины программиста. Например, в CLOS мультиметоды одновременно являются функциями первого класса , что позволяет рассматривать их одновременно и как связанно квантифицированные , и как обобщённые (истинно полиморфные).
- Традиционные ОО-языки используют номинативную типизацию , то есть допустимость соиспользования объектов разных классов только при условии явного указания родственных отношений между классами. Для полиморфно типизированных языков характерна структурная типизация , то есть согласование классов между собой тем же механизмом, что и согласование числа 5 с типом int . Динамически типизированные языки также занимают здесь промежуточную позицию.
Обобщённое обоснование динамической диспетчеризации (включая множественную) в середине 1990-х годов построил Джузеппе Кастанья .
История
ООП возникло в результате развития идеологии процедурного программирования , где данные и подпрограммы (процедуры, функции) их обработки формально не связаны. Для дальнейшего развития объектно-ориентированного программирования часто большое значение имеют понятия события (так называемое событийно-ориентированное программирование) и компонента (компонентное программирование , КОП).
Взаимодействие объектов происходит посредством . Результатом дальнейшего развития ООП, по-видимому, будет агентно-ориентированое программирование , где агенты - независимые части кода на уровне выполнения. Взаимодействие агентов происходит посредством изменения среды , в которой они находятся.
Языковые конструкции, конструктивно не относящиеся непосредственно к объектам, но сопутствующие им для их безопасной (исключительные ситуации , проверки) и эффективной работы, инкапсулируются от них в аспекты (в аспектно-ориентированном программировании). Субъектно-ориентированное программирование расширяет понятие объекта посредством обеспечения более унифицированного и независимого взаимодействия объектов. Может являться переходной стадией между ООП и агентным программированием в части самостоятельного их взаимодействия.
Первым языком программирования, в котором были предложены основные понятия, впоследствии сложившиеся в парадигму, была Симула , но термин «объектная ориентированность» не использовался в контексте использования этого языка. В момент его появления в 1967 году в нём были предложены революционные идеи: объекты, классы, виртуальные методы и др., однако это всё не было воспринято современниками как нечто грандиозное. Фактически, Симула была «Алголом с классами», упрощающим выражение в процедурном программировании многих сложных концепций. Понятие класса в Симуле может быть полностью определено через композицию конструкций Алгола (то есть класс в Симуле - это нечто сложное, описываемое посредством примитивов).
Взгляд на программирование «под новым углом» (отличным от процедурного) предложили Алан Кэй и Дэн Ингаллс в языке Smalltalk . Здесь понятие класса стало основообразующей идеей для всех остальных конструкций языка (то есть класс в Смолтоке является примитивом, посредством которого описаны более сложные конструкции). Именно он стал первым широко распространённым объектно-ориентированным языком программирования .
В настоящее время количество прикладных языков программирования (список языков), реализующих объектно-ориентированную парадигму, является наибольшим по отношению к другим парадигмам. Наиболее распространённые в промышленности языки (С++, Delphi, C#, Java и др.) воплощают объектную модель Симулы. Примерами языков, опирающихся на модель Смолтока, являются Objective-C, Python, Ruby.
Определение ООП и его основные концепции
В центре ООП находится понятие объекта. Объект - это сущность, которой можно посылать сообщения и которая может на них реагировать, используя свои данные. Объект - это экземпляр класса. Данные объекта скрыты от остальной программы. Инкапсуляция включает в себя сокрытие (Но им не является!).
Наличие инкапсуляции достаточно для объектности языка программирования, но ещё не означает его объектной ориентированности - для этого требуется наличие наследования .
Но даже наличие инкапсуляции и наследования не делает язык программирования в полной мере объектным с точки зрения ООП. Основные преимущества ООП проявляются только в том случае, когда в языке программирования реализован полиморфизм подтипов - возможность единообразно обрабатывать объекты с различной реализацией при условии наличия общего интерфейса.
Сложности определения
ООП имеет уже более чем сорокалетнюю историю, но, несмотря на это, до сих пор не существует чёткого общепринятого определения данной технологии . Основные принципы, заложенные в первые объектные языки и системы, подверглись существенному изменению (или искажению) и дополнению при многочисленных реализациях последующего времени. Кроме того, примерно с середины 1980-х годов термин «объектно-ориентированный» стал модным , в результате с ним произошло то же самое, что несколько раньше с термином «структурный» (ставшим модным после распространения технологии структурного программирования) - его стали искусственно «прикреплять» к любым новым разработкам, чтобы обеспечить им привлекательность. Бьёрн Страуструп в 1988 году писал, что обоснование «объектной ориентированности» чего-либо, в большинстве случаев, сводится к некорректному силлогизму : «X - это хорошо. Объектная ориентированность - это хорошо. Следовательно , X является объектно-ориентированным».
Роджер Кинг аргументированно настаивал, что его кот является объектно-ориентированным. Кроме прочих своих достоинств, кот демонстрирует характерное поведение, реагирует на сообщения, наделён унаследованными реакциями и управляет своим, вполне независимым, внутренним состоянием.
Однако общность механизма обмена сообщениями имеет и другую сторону - «полноценная» передача сообщений требует дополнительных накладных расходов, что не всегда приемлемо. Поэтому во многих современных объектно-ориентированных языках программирования используется концепция «отправка сообщения как вызов метода» - объекты имеют доступные извне методы, вызовами которых и обеспечивается взаимодействие объектов. Данный подход реализован в огромном количестве языков программирования, в том числе C++ , Object Pascal , Java , Oberon-2 . Однако, это приводит к тому, что сообщения уже не являются самостоятельными объектами, и, как следствие, не имеют атрибутов, что сужает возможности программирования. Некоторые языки используют гибридное представление, демонстрируя преимущества одновременно обоих подходов - например, CLOS , Python .
Концепция виртуальных методов , поддерживаемая этими и другими современными языками, появилась как средство обеспечить выполнение нужных методов при использовании полиморфных переменных, то есть, по сути, как попытка расширить возможности вызова методов для реализации части функциональности, обеспечиваемой механизмом обработки сообщений.
Особенности реализации
Как уже говорилось выше, в современных объектно-ориентированных языках программирования каждый объект является значением, относящимся к определённому классу . Класс представляет собой объявленный программистом составной тип данных , имеющий в составе:
Поля данных Параметры объекта (конечно, не все, а только необходимые в программе), задающие его состояние (свойства объекта предметной области). Иногда поля данных объекта называют свойствами объекта, из-за чего возможна путаница. Фактически поля представляют собой значения (переменные, константы), объявленные как принадлежащие классу. Методы Процедуры и функции, связанные с классом. Они определяют действия, которые можно выполнять над объектом такого типа, и которые сам объект может выполнять.Классы могут наследоваться друг от друга. Класс-потомок получает все поля и методы класса-родителя, но может дополнять их собственными либо переопределять уже имеющиеся. Большинство языков программирования поддерживает только единичное наследование (класс может иметь только один класс-родитель), лишь в некоторых допускается множественное наследование - порождение класса от двух или более классов-родителей. Множественное наследование создаёт целый ряд проблем, как логических, так и чисто реализационных, поэтому в полном объёме его поддержка не распространена. Вместо этого в 1990-е годы появилось и стало активно вводиться в объектно-ориентированные языки понятие интерфейса . Интерфейс - это класс без полей и без реализации, включающий только заголовки методов. Если некий класс наследует (или, как говорят, реализует) интерфейс, он должен реализовать все входящие в него методы. Использование интерфейсов предоставляет относительно дешёвую альтернативу множественному наследованию.
Взаимодействие объектов в абсолютном большинстве случаев обеспечивается вызовом ими методов друг друга.
Инкапсуляция обеспечивается следующими средствами:
Контроль доступа Поскольку методы класса могут быть как чисто внутренними, обеспечивающими логику функционирования объекта, так и внешними, с помощью которых взаимодействуют объекты, необходимо обеспечить скрытость первых при доступности извне вторых. Для этого в языки вводятся специальные синтаксические конструкции, явно задающие область видимости каждого члена класса. Традиционно это модификаторы public, protected и private, обозначающие, соответственно, открытые члены класса, члены класса, доступные внутри класса и из классов-потомков, и скрытые, доступные только внутри класса. Конкретная номенклатура модификаторов и их точный смысл различаются в разных языках. Методы доступа Поля класса в общем случае не должны быть доступны извне, поскольку такой доступ позволил бы произвольным образом менять внутреннее состояние объектов. Поэтому поля обычно объявляются скрытыми (либо язык в принципе не позволяет обращаться к полям класса извне), а для доступа к находящимся в полях данным используются специальные методы, называемые методами доступа. Такие методы либо возвращают значение того или иного поля, либо производят запись в это поле нового значения. При записи метод доступа может проконтролировать допустимость записываемого значения и, при необходимости, произвести другие манипуляции с данными объекта, чтобы они остались корректными (внутренне согласованными). Методы доступа называют ещё аксессорами (от англ. access - доступ), а по отдельности - геттерами (англ. get - чтение) и сеттерами (англ. set - запись) . Свойства объекта Псевдополя, доступные для чтения и/или записи. Свойства внешне выглядят как поля и используются аналогично доступным полям (с некоторыми исключениями), однако фактически при обращении к ним происходит вызов методов доступа. Таким образом, свойства можно рассматривать как «умные» поля данных, сопровождающие доступ к внутренним данным объекта какими-либо дополнительными действиями (например, когда изменение координаты объекта сопровождается его перерисовкой на новом месте). Свойства, по сути, не более чем синтаксический сахар , поскольку никаких новых возможностей они не добавляют, а лишь скрывают вызов методов доступа. Конкретная языковая реализация свойств может быть разной. Например, в объявление свойства непосредственно содержит код методов доступа, который вызывается только при работе со свойствами, то есть не требует отдельных методов доступа, доступных для непосредственного вызова. В Delphi объявление свойства содержит лишь имена методов доступа, которые должны вызываться при обращении к полю. Сами методы доступа представляют собой обычные методы с некоторыми дополнительными требованиями к сигнатуре .
Полиморфизм реализуется путём введения в язык правил, согласно которым переменной типа «класс» может быть присвоен объект любого класса-потомка её класса.
Проектирование программ в целом
ООП ориентировано на разработку крупных программных комплексов, разрабатываемых командой программистов (возможно, достаточно большой). Проектирование системы в целом, создание отдельных компонентов и их объединение в конечный продукт при этом часто выполняется разными людьми, и нет ни одного специалиста, который знал бы о проекте всё.
Объектно-ориентированное проектирование ориентируется на описание структуры проектируемой системы (приоритетно по отношению к описанию её поведения, в отличие от функционального программирования), то есть, фактически, в ответе на два основных вопроса:
- Из каких частей состоит система ;
- В чём состоит ответственность каждой из её частей .
Выделение частей производится таким образом, чтобы каждая имела минимальный по объёму и точно определённый набор выполняемых функций (обязанностей), и при этом взаимодействовала с другими частями как можно меньше.
Дальнейшее уточнение приводит к выделению более мелких фрагментов описания. По мере детализации описания и определения ответственности выявляются данные, которые необходимо хранить, наличие близких по поведению агентов, которые становятся кандидатами на реализацию в виде классов с общими предками. После выделения компонентов и определения интерфейсов между ними реализация каждого компонента может проводиться практически независимо от остальных (разумеется, при соблюдении соответствующей технологической дисциплины).
Большое значение имеет правильное построение иерархии классов. Одна из известных проблем больших систем, построенных по ООП-технологии - так называемая проблема хрупкости базового класса . Она состоит в том, что на поздних этапах разработки, когда иерархия классов построена и на её основе разработано большое количество кода, оказывается трудно или даже невозможно внести какие-либо изменения в код базовых классов иерархии (от которых порождены все или многие работающие в системе классы). Даже если вносимые изменения не затронут интерфейс базового класса, изменение его поведения может непредсказуемым образом отразиться на классах-потомках. В случае крупной системы разработчик базового класса просто не в состоянии предугадать последствия изменений, он даже не знает о том, как именно базовый класс используется и от каких особенностей его поведения зависит корректность работы классов-потомков.
Различные ООП-методологии
Компонентное программирование - следующий этап развития ООП; прототип- и класс-ориентированное программирование - разные подходы к созданию программы, которые могут комбинироваться, имеющие свои преимущества и недостатки.
Компонентное программирование
Компонентно-ориентированное программирование - это своеобразная «надстройка» над ООП, набор правил и ограничений, направленных на построение крупных развивающихся программных систем с большим временем жизни. Программная система в этой методологии представляет собой набор компонентов с хорошо определёнными интерфейсами. Изменения в существующую систему вносятся путём создания новых компонентов в дополнение или в качестве замены ранее существующих. При создании новых компонентов на основе ранее созданных запрещено использование наследования реализации - новый компонент может наследовать лишь интерфейсы базового. Таким образом, компонентное программирование обходит проблему хрупкости базового класса.
Прототипное программирование
Прототипное программирование , сохранив часть черт ООП, отказалось от базовых понятий - класса и наследования.
- Прототип - это объект-образец, по образу и подобию которого создаются другие объекты. Объекты-копии могут сохранять связь с родительским объектом, автоматически наследуя изменения в прототипе; эта особенность определяется в рамках конкретного языка .
- Вместо механизма описания классов и порождения экземпляров, язык предоставляет механизм создания объекта (путём задания набора полей и методов, которые объект должен иметь) и механизм клонирования объектов.
- Каждый вновь созданный объект является «экземпляром без класса». Каждый объект может стать прототипом - быть использован для создания нового объекта с помощью операции клонирования . После клонирования новый объект может быть изменён, в частности, дополнен новыми полями и методами.
- Клонированный объект либо становится полной копией прототипа, хранящей все значения его полей и дублирующей его методы, либо сохраняет ссылку на прототип, не включая в себя клонированных полей и методов до тех пор, пока они не будут изменены. В последнем случае среда исполнения обеспечивает механизм делегирования - если при обращении к объекту он сам не содержит нужного метода или поля данных, вызов передаётся прототипу, от него, при необходимости - дальше по цепочке.
Класс-ориентированное программирование
Класс-ориентированное программирование - это программирование, сфокусированное на данных, причём данные и поведение неразрывно связаны между собой. Вместе данные и поведение представляют собой класс. Соответственно в языках, основанных на понятии «класс», все объекты разделены на два основных типа - классы и экземпляры. Класс определяет структуру и функциональность (поведение), одинаковую для всех экземпляров данного класса. Экземпляр является носителем данных - то есть обладает состоянием, меняющимся в соответствии с поведением, заданным классом. В класс-ориентированных языках новый экземпляр создаётся через вызов конструктора класса (возможно, с набором параметров). Получившийся экземпляр имеет структуру и поведение, жёстко заданные его классом.
Производительность объектных программ
Гради Буч указывает на следующие причины, приводящие к снижению производительности программ из-за использования объектно-ориентированных средств:
Динамическое связывание методов Обеспечение полиморфного поведения объектов приводит к необходимости связывать методы, вызываемые программой (то есть определять, какой конкретно метод будет вызываться) не на этапе компиляции, а в процессе исполнения программы, на что тратится дополнительное время. При этом реально динамическое связывание требуется не более чем для 20 % вызовов, но некоторые ООП-языки используют его постоянно. Значительная глубина абстракции ООП-разработка часто приводит к созданию «многослойных» приложений, где выполнение объектом требуемого действия сводится к множеству обращений к объектам более низкого уровня. В таком приложении происходит очень много вызовов методов и возвратов из методов, что, естественно, сказывается на производительности. Наследование «размывает» код Код, относящийся к «конечным» классам иерархии наследования, которые обычно и используются программой непосредственно, находится не только в самих этих классах, но и в их классах-предках. Относящиеся к одному классу методы фактически описываются в разных классах. Это приводит к двум неприятным моментам:
- Снижается скорость трансляции, так как компоновщику приходится подгружать описания всех классов иерархии.
- Снижается производительность программы в системе со страничной памятью - поскольку методы одного класса физически находятся в разных местах кода, далеко друг от друга, при работе фрагментов программы, активно обращающихся к унаследованным методам, система вынуждена производить частые переключения страниц.
Несмотря на отмеченные недостатки, Буч утверждает, что выгоды от использования ООП более весомы. Кроме того, повышение производительности за счёт лучшей организации ООП-кода, по его словам, в некоторых случаях компенсирует дополнительные накладные расходы на организацию функционирования программы. Можно также заметить, что многие эффекты снижения производительности могут сглаживаться или даже полностью устраняться за счёт качественной оптимизации кода компилятором. Например, упомянутое выше снижение скорости доступа к полям класса из-за использования методов доступа устраняется, если компилятор вместо вызова метода доступа использует инлайн-подстановку (современные компиляторы делают это вполне уверенно).
Критика ООП
Несмотря на отдельные критические замечания в адрес ООП, в настоящее время именно эта парадигма используется в подавляющем большинстве промышленных проектов. Однако нельзя считать, что ООП является наилучшей из методик программирования во всех случаях.
Критические высказывания в адрес ООП:
- Было показано отсутствие значимой разницы в продуктивности разработки программного обеспечения между ООП и процедурным подходом .
- Кристофер Дэйт указывает на невозможность сравнения ООП и других технологий во многом из-за отсутствия строгого и общепризнанного определения ООП .
- Александр Степанов в одном из своих интервью указывал, что ООП «методологически неправильно» и что «…ООП практически такая же мистификация , как и искусственный интеллект …» .
- Фредерик Брукс указывает, что наиболее сложной частью создания программного обеспечения является «…спецификация, дизайн и тестирование концептуальных конструкций, а отнюдь не работа по выражению этих концептуальных конструкций…». ООП (наряду с такими технологиями как искусственный интеллект, верификация программ, автоматическое программирование, графическое программирование , экспертные системы и др.), по его мнению, не является «серебряной пулей», которая могла бы на порядок величины снизить сложность разработки программных систем. Согласно Бруксу, «…ООП позволяет сократить только привнесённую сложность в выражение дизайна. Дизайн остаётся сложным по своей природе…» .
- Эдсгер Дейкстра указывал: «…то, о чём общество в большинстве случаев просит - это эликсир от всех болезней. Естественно, „эликсир“ имеет очень впечатляющие названия, иначе будет очень трудно что-то продать: „Структурный анализ и Дизайн“, „Программная инженерия“, „Модели зрелости“, „Управляющие информационные системы“ (Management Information Systems), „Интегрированные среды поддержки проектов“, „Объектная ориентированность“, „Реинжиниринг бизнес-процессов “…» .
- Никлаус Вирт считает, что ООП - не более чем тривиальная надстройка над структурным программированием [ ] , и преувеличение её значимости, выражающееся, в том числе, во включении в языки программирования всё новых модных «объектно-ориентированных» средств, вредит качеству разрабатываемого программного обеспечения.
- Патрик Киллелиа в своей книге «Тюнинг веб-сервера» писал: «…ООП предоставляет вам множество способов замедлить работу ваших программ…».
- Известная обзорная статья проблем современного ООП-программирования перечисляет некоторые типичные проблемы ООП [ ] .
- В программистском фольклоре получила широкое распространение критика объектно-ориентированного подхода в сравнении с функциональным подходом с использованием метафоры «Королевства Существительных » из эссе Стива Йегги .
Если попытаться классифицировать критические высказывания в адрес ООП, можно выделить несколько аспектов критики данного подхода к программированию.
Критика рекламы ООП Критикуется явно высказываемое или подразумеваемое в работах некоторых пропагандистов ООП, а также в рекламных материалах «объектно-ориентированных» средств разработки представление об объектном программировании как о некоем всемогущем подходе, который магическим образом устраняет сложность программирования. Как замечали многие, в том числе упомянутые выше Брукс и Дейкстра, «серебряной пули не существует» - независимо от того, какой парадигмы программирования придерживается разработчик, создание нетривиальной сложной программной системы всегда сопряжено со значительными затратами интеллектуальных ресурсов и времени. Из наиболее квалифицированных специалистов в области ООП никто, как правило, не отрицает справедливость критики этого типа. Оспаривание эффективности разработки методами ООП Критики оспаривают тезис о том, что разработка объектно-ориентированных программ требует меньше ресурсов или приводит к созданию более качественного ПО. Проводится сравнение затрат на разработку разными методами, на основании которого делается вывод об отсутствии у ООП преимуществ в данном направлении. Учитывая крайнюю сложность объективного сравнения различных разработок, подобные сопоставления, как минимум, спорны. С другой стороны, получается, что ровно так же спорны и утверждения об эффективности ООП. Производительность объектно-ориентированных программ Указывается на то, что целый ряд «врождённых особенностей» ООП-технологии делает построенные на её основе программы технически менее эффективными, по сравнению с аналогичными необъектными программами. Не отрицая действительно имеющихся дополнительных накладных расходов на организацию работы ООП-программ (см. раздел «Производительность» выше), нужно, однако, отметить, что значение снижения производительности часто преувеличивается критиками. В современных условиях, когда технические возможности компьютеров чрезвычайно велики и постоянно растут, для большинства прикладных программ техническая эффективность оказывается менее существенна, чем функциональность, скорость разработки и сопровождаемость. Лишь для некоторого, очень ограниченного класса программ (ПО встроенных систем, драйверы устройств, низкоуровневая часть системного ПО, научное ПО) производительность остаётся критическим фактором. Критика отдельных технологических решений в ООП-языках и библиотеках Эта критика многочисленна, но затрагивает она не ООП как таковое, а приемлемость и применимость в конкретных случаях тех или иных реализаций её механизмов. Одним из излюбленных объектов критики является язык C++, входящий в число наиболее распространённых промышленных ООП-языков.
Объектно-ориентированные языки
Многие современные языки специально созданы для облегчения объектно-ориентированного программирования. Однако следует отметить, что можно применять техники ООП и для не-объектно-ориентированного языка и наоборот, применение объектно-ориентированного языка вовсе не означает, что код автоматически становится объектно-ориентированным.
По мере совершенствования вычислительной техники компьютеры стали использоваться для решения все более и более сложных задач. Оказалось, что для решения сложных задач важна не только повышенная вычислительная мощность компьютеров, но и эффективность написания сложных программ. Объектно-ориентированное программирование (ООП) появилось именно как эффективный способ преодоления трудностей, возникающих при создании сложных программ.
Наиболее значимой частью ООП является особый подход к решению сложных задач программирования, называемый объектно-ориентированным анализом , а объектно-ориентированные языки программирования - просто удобные инструменты для реализации этого подхода.
История
Основатели ООП - выдающиеся норвежские ученые Кристен Нигаард (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++
и т.п.) познакомить с понятием объектов и их использованием в основном с помощью методики обучения программированию “по образцу”.
Из своего опыта могу сказать, что всегда считал что понимал ООП, что же тут такого то - полиморфизм, инкапсуляция и наследование, но вот когда дошло до дела, то туговато пришлось. Хочу разложить всё по полочкам чтобы никто не наступил на мои грабли в будущем:)
Шаг 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.
Наверное, в половине вакансий(если не больше), требуется знание и понимание ООП. Да, эта методология, однозначно, покорила многих программистов! Обычно понимание ООП приходит с опытом, поскольку годных и доступно изложенных материалов на данный счет практически нет. А если даже и есть, то далеко не факт, что на них наткнутся читатели. Надеюсь, у меня получится объяснить принципы этой замечательной методологии, как говорится, на пальцах.
Итак, уже в начале статьи я уже упомянул такой термин "методология". Применительно к программированию этот термин подразумевает наличие какого-либо набора способов организации кода, методов его написания, придерживаясь которых, программист сможет писать вполне годные программы.
ООП (или объектно-ориентированное программирование) представляет собой способ организации кода программы, когда основными строительными блоками программы являются объекты и классы, а логика работы программы построена на их взаимодействии.
Об объектах и классах
Класс - это такая структура данных, которую может формировать сам программист. В терминах ООП, класс состоит из полей (по-простому - переменных) и методов (по-простому - функций). И, как выяснилось, сочетание данных и функций работы над ними в одной структуре дает невообразимую мощь. Объект - это конкретный экземпляр класса. Придерживаясь аналогии класса со структурой данных, объект - это конкретная структура данных, у которой полям присвоены какие-то значения. Поясню на примере:
Допустим, нам нужно написать программу, рассчитывающую периметр и площадь треугольника, который задан двумя сторонами и углом между ними. Для написания такой программы используя ООП, нам необходимо будет создать класс (то есть структуру) Треугольник. Класс Треугольник будет хранить три поля (три переменные): сторона А, сторона Б, угол между ними; и два метода (две функции): посчитать периметр, посчитать площадь. Данным классом мы можем описать любой треугольник и вычислить периметр и площадь. Так вот, конкретный треугольник с конкретными сторонами и углом между ними будет называться экземпляром класса Треугольник. Таким образом класс - это шаблон, а экземпляр - конкретная реализация шаблона. А вот уже экземпляры являются объектами, то есть конкретными элементами, хранящими конкретные значения.
Одним из самых распространенных объектно-ориентированных языков программирования является язык java. Там без использования объектов просто не обойтись. Вот как будет выглядеть код класса, описывающего треугольник на этом языке:
/** * Класс Треугольник. */ class Triangle { /** * Специальный метод, называемый конструктор класса. * Принимает на вход три параметра: * длина стороны А, длина стороны Б, * угол между этими сторонами(в градусах) */ Triangle(double sideA, double sideB, double angleAB) { this.sideA = sideA; this.sideB = sideB; this.angleAB = angleAB; } double sideA; //Поле класса, хранит значение стороны А в описываемом треугольнике double sideB; //Поле класса, хранит значение стороны Б в описываемом треугольнике double angleAB; //Поле класса, хранит угла(в градусах) между двумя сторонами в описываемом треугольнике /** * Метод класса, который рассчитывает площадь треугольника */ double getSquare() { double square = this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180); return square; } /** * Метод класса, который рассчитывает периметр треугольника */ double getPerimeter() { double sideC = Math.sqrt(Math.pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180)); double perimeter = this.sideA + this.sideB + sideC; return perimeter; } }
Если мы внутрь класса добавим следующий код:
/** * Именно в этом месте запускается программа */ public static void main(String args) { //Значения 5, 17, 35 попадают в конструктор класса Triangle Triangle triangle1 = new Triangle(5, 17, 35); System.out.println("Площадь треугольника1: "+triangle1.getSquare()); System.out.println("Периметр треугольника1: "+triangle1.getPerimeter()); //Значения 6, 8, 60 попадают в конструктор класса Triangle Triangle triangle2 = new Triangle(6, 8, 60); System.out.println("Площадь треугольника1: "+triangle2.getSquare()); System.out.println("Периметр треугольника1: "+triangle2.getPerimeter()); }
то программу уже можно будет запускать на выполнение. Это особенность языка java. Если в классе есть такой метод
Public static void main(String args)
то этот класс можно выполнять. Разберем код подробнее. Начнем со строки
Triangle triangle1 = new Triangle(5, 17, 35);
Здесь мы создаем экземпляр triangle1 класса Triangle и тут же задаем ему параметры сторон и угла между ними. При этом, вызывается специальный метод, называемый конструктор и заполняет поля объекта переданными значениями в конструктор. Ну, а строки
System.out.println("Площадь треугольника1: "+triangle1.getSquare()); System.out.println("Периметр треугольника1: "+triangle1.getPerimeter());
выводят рассчитанные площадь треугольника и его периметр в консоль.
Аналогично все происходит и для второго экземпляра класса Triangle .
Понимание сути классов и конструирования конкретных объектов - это уверенный первый шаг к пониманию методологии ООП.
Еще раз, самое важное:
ООП - это способ организации кода программы;
Класс - это пользовательская структура данных, которая воедино объединяет данные и функции для работы с ними(поля класса и методы класса);
Объект - это конкретный экземпляр класса, полям которого заданы конкретные значения.
Три волшебных слова
ООП включает три ключевых подхода: наследование, инкапсуляцию и полиморфизм. Для начала, приведу определения из wikipedia :
Инкапсуляция - свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Некоторые языки (например, С++) отождествляют инкапсуляцию с сокрытием, но большинство (Smalltalk, Eiffel, OCaml) различают эти понятия.
Наследование - свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс - потомком, наследником, дочерним или производным классом.
Полиморфизм - свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Понять, что же все эти определения означают на деле достаточно сложно. В специализированных книгах, раскрывающих данную тему на каждое определение, зачастую, отводится целая глава, но, как минимум, абзац. Хотя, сути того, что нужно понять и отпечатать
навсегда в своем мозге программиста совсем немного.
А примером для разбора нам будут служить фигуры на плоскости. Из школьной геометрии мы знаем, что у всех фигур, описанных на плоскости, можно рассчитать периметр и площадь. Например, для точки
оба параметра равны нулю. Для отрезка мы можем вычислить лишь периметр. А для квадрата, прямоугольника или треугольника - и то, и другое. Сейчас же мы опишем эту задачу в терминах ООП. Также не лишним будет уловить цепь рассуждений, которые выливаются
в иерархию классов, которая, в свою очередь, воплощается в работающий код. Поехали:
Итак, точка - это самая малая геометрическая фигура, которая является основой всех прочих построений (фигур). Поэтому именно точка выбрана в качестве базового родительского класса. Напишем класс точки на java:
/** * Класс точки. Базовый класс */ class Point { /** * Пустой конструктор */ Point() {} /** * Метод класса, который рассчитывает площадь фигуры */ double getSquare() { return 0; } /** * Метод класса, который рассчитывает периметр фигуры */ double getPerimeter() { return 0; } /** * Метод класса, возвращающий описание фигуры */ String getDescription() { return "Точка"; } }
У получившегося класса Point пустой конструктор, поскольку в данном примере мы работаем без конкретных координат, а оперируем только параметрами значениями сторон. Так как у точки нет никаких сторон, то и передавать ей никаких параметров не надо. Также заметим, что класс имеет методы Point::getSquare() и Point::getPerimeter() для расчета площади и периметра, оба возвращают 0. Для точки оно и логично.
Поскольку у нас точка является основой всех прочих фигур, то и классы этих прочих фигур мы наследуем от класса Point . Опишем класс отрезка, наследуемого от класса точки:
/** * Класс Отрезок */ class LineSegment extends Point { LineSegment(double segmentLength) { this.segmentLength = segmentLength; } double segmentLength; // Длина отрезка /** * Переопределенный метод класса, который рассчитывает площадь отрезка */ double getSquare() { return 0; } /** * Переопределенный метод класса, который рассчитывает периметр отрезка */ double getPerimeter() { return this.segmentLength; } String getDescription() { return "Отрезок длиной: " + this.segmentLength; } }
Class LineSegment extends Point
означает, что класс LineSegment наследуется от класса Point . Методы LineSegment::getSquare() и LineSegment::getPerimeter() переопределяют соответствующие методы базового класса. Площадь отрезка всегда равняется нулю, а площадь периметра равняется длине этого отрезка.
Теперь, подобно классу отрезка, опишем класс треугольника(который также наследуется от класса точки):
/** * Класс Треугольник. */ class Triangle extends Point { /** * Конструктор класса. Принимает на вход три параметра: * длина стороны А, длина стороны Б, * угол между этими сторонами(в градусах) */ Triangle(double sideA, double sideB, double angleAB) { this.sideA = sideA; this.sideB = sideB; this.angleAB = angleAB; } double sideA; //Поле класса, хранит значение стороны А в описываемом треугольнике double sideB; //Поле класса, хранит значение стороны Б в описываемом треугольнике double angleAB; //Поле класса, хранит угла(в градусах) между двумя сторонами в описываемом треугольнике /** * Метод класса, который рассчитывает площадь треугольника */ double getSquare() { double square = (this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180))/2; return square; } /** * Метод класса, который рассчитывает периметр треугольника */ double getPerimeter() { double sideC = Math.sqrt(Math.pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this.angleAB * Math.PI / 180)); double perimeter = this.sideA + this.sideB + sideC; return perimeter; } String getDescription() { return "Треугольник со сторонами: " + this.sideA + ", " + this.sideB + " и углом между ними: " + this.angleAB; } }
Тут нет ничего нового. Также, методы Triangle::getSquare()
и Triangle::getPerimeter()
переопределяют соответствующие методы базового класса.
Ну а теперь,
собственно, тот самый код, который показывает волшебство полиморифзма и раскрывает мощь ООП:
Class Main { /** * Именно в этом месте запускается программа */ public static void main(String args) { //ArrayList - Это специальная структура данных в java, // позволяющая хранить объекты определенного типа в массиве. ArrayList figures = new ArrayList(); //добавляем три разных объекта в массив figures figures.add(new Point()); figures.add(new LineSegment(133)); figures.add(new Triangle(10, 17, 55)); for (int i = 0; i
Мы создали массив объектов класса Point , а поскольку классы LineSegment и Triangle наследуются от класса Point , то и их мы можем помещать в этот массив. Получается, каждую фигуру, которая есть в массиве figures мы можем рассматривать как объект класса Point . В этом и заключается полиморфизм: неизвестно, к какому именно классу принадлежат находящиеся в массиве figures объекты, но поскольку все объекты внутри этого массива принадлежат одному базовому классу Point , то все методы, которые применимы к классу Point также и применимы к его классам-наследникам.
Теперь о инкапсуляции. То, что мы поместили в одном классе параметры фигуры и методы расчета площади и периметра - это и есть инкапсуляция, мы инкапсулировали фигуры в отдельные классы. То, что у нас для расчета периметра используется специальный метод в классе - это и есть инкапсуляцию, мы инкапсулировали расчет периметра в метод getPerimiter() . Иначе говоря, инкапсуляция - это сокрытие реализции (пожалуй, самое короткое, и в то же время емкое определением инкапсуляции).
Полный код примера:
Import java.util.ArrayList; class Main { /** * Именно в этом месте запускается программа */ public static void main(String args) { //ArrayList - Это специальная структура данных в java, // позволяющая хранить объекты определенного типа в массиве. ArrayList figures = new ArrayList(); //добавляем три разных объекта в массив figures figures.add(new Point()); figures.add(new LineSegment(133)); figures.add(new Triangle(10, 17, 55)); for (int i = 0; i
Объектно-ориентированное программирование (ООП) позволяет разложить проблему на составные части, каждая из которых становится самостоятельным объектом. Каждый из объектов содержит свой собственный код и данные, которые относятся к этому объекту.
Любая программа, написанная на языке ООП, отражает в своих данных состояние физических предметов либо абстрактных понятий – объектов программирования, для работы, с которыми она предназначена.
Все данные об объекте программирования и его связях с другими объектами можно объединить в одну структурированную переменную. В первом приближении ее можно назвать объектом.
С объектом связывается набор действий, иначе называемых методами . С точки зрения языка программирования набор действий или методов – это функции, получающие в качестве обязательного параметра указатель на объект и выполняющие определенные действия с данными объекта программирования. Технология ООП запрещает работать с объектом иначе, чем через методы, таким образом, внутренняя структура объекта скрыта от внешнего пользователя.
Описание множества однотипных объектов называется классом
.
Объект
– это структурированная переменная, содержащая всю информацию о некотором физическом предмете или реализуемом в программе понятии.
Класс – это описание множества объектов программирования (объектов) и выполняемых над ними действий.
Класс можно сравнить с чертежом, согласно которому создаются объекты. Обычно классы разрабатывают таким образом, чтобы их объекты соответствовали объектам предметной области решаемой задачи.
Основные понятия объектно-ориентированного программирования
Любая функция в программе представляет собой метод для объекта некоторого класса.
Класс должен формироваться в программе естественным образом, как только в ней возникает необходимость описания новых объектов программирования. Каждый новый шаг в разработке алгоритма должен представлять собой разработку нового класса на основе уже существующих.
Вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить).
Программирование «от класса к классу» включает в себя ряд новых понятий. Основными понятиями ООП являются
- инкапсуляция;
- наследование;
- полиморфизм.
Инкапсуляция данных (от «капсула») – это механизм, который объединяет данные и код, манипулирующий с этими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В ООП код и данные могут быть объединены вместе (в так называемый «черный ящик») при создании объекта.
Внутри объекта коды и данные могут быть закрытыми или открытыми.
Закрытые коды или данные доступны только для других частей того же самого объекта и, соответственно, недоступны для тех частей программы, которые существуют вне объекта.
Открытые коды и данные, напротив, доступны для всех частей программы, в том числе и для других частей того же самого объекта.
Наследование . Новый, или производный класс может быть определен на основе уже имеющегося, или базового класса.
При этом новый класс сохраняет все свойства старого: данные объекта базового класса включаются в данные объекта производного, а методы базового класса могут быть вызваны для объекта производного класса, причем они будут выполняться над данными включенного в него объекта базового класса.
Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки.
Если объект наследует свои свойства от одного родителя, то говорят об одиночном наследовании . Если объект наследует данные и методы от нескольких базовых классов, то говорят о множественном наследовании .
Пример наследования – определение структуры, отдельный член которой является ранее определенной структурой.
Полиморфизм – это свойство, которое позволяет один и тот же идентификатор (одно и то же имя) использовать для решения двух и более схожих, но технически разных задач.
Целью полиморфизма, применительно к ООП, является использование одного имени для задания действий, общих для ряда классов объектов. Такой полиморфизм основывается на возможности включения в данные объекта также и информации о методах их обработки (в виде указателей на функции).
Будучи доступным в некоторой точке программы, объект, даже при отсутствии полной информации о его типе, всегда может корректно вызвать свойственные ему методы.
Полиморфная функция
– это семейство функций с одним и тем же именем, но выполняющие различные действия в зависимости от условий вызова.
Например, нахождение абсолютной величины в языке Си требует трех разных функций с разными именами.


