Тарифы Услуги Сим-карты

Понимание принципов объектно ориентированного программирования. Объектно-ориентированное программирование (ООП). Инкапсуляция и управление доступом

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

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

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

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

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

Основные понятия ООП

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

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

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

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

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

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

В дальнейшем, несмотря на то, что слово «пользователь» ассоциируется с пасьянсом «Косынка» и «Microsoft Word», мы будем называть пользователями тех программистов, которые используют ваш класс, включая вас самих. Человека, который является автором класса, мы будем называть разработчиком.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Существует определенный формат программного кода, задающего установку свойства и использование метода:

Объект.Свойство = Значение

Объект.Метод [Параметр1 [...]]

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

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

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

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

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

Свойства

Рис.1. Иллюстрация понятия инкапсуляции

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

Свойствами класса Roundявляются следующие:

R – радиус круга;

X ,Y – координаты центра круга;

Color – цвет круга.

Методы класса Round:

– Draw– рисует круг с заданными параметрами;

– Move– перемещает круг на определенное расстояние в выбранном направлении;

– ChangeColor– изменяет цвет крута.

Для того чтобы нарисовать снеговика, потребуются три объекта-круга. Верхний из них можно назвать Head, средний –Body,aнижний –Foot. Все эти объекты принадлежат классуRound. Следовательно, все они имеют одинаковые свойства (R ,X ,Y ,Color ) и вызывают одинаковые методы (Draw,Move,ChangeColor) в ответ на одни и те же запросы.

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

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

Две методики

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

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

Абстракция

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

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

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

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

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

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

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

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

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

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

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

Рис. 1.

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

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

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

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

Рис. 2.

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

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

Полиморфизм

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

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

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

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

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

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

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

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

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

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

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

Что такое объектно ориентированное программирование

Объектно-ориентированное программирование — это стиль кодирования, который позволяет разработчику группировать схожие задачи в классы . Таким образом код соответствует принципу DRY (don"t repeat yourself - не повторяй самого себя) и становится лёгким для сопровождения.

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

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

Что такое объекты и классы

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

В чем заключается разница между классами и объектами

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

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

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

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

Структура класса

Синтаксис для создания класса очень прост: для объявления класса используется ключевое слово class , за которым следует имя класса и набор фигурных скобок ({}):

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

$obj = new MyClass;

Чтобы увидеть содержимое объекта, используйте var_dump() :

Var_dump($obj);

Можно протестировать весь процесс, скопировав весь код в файл test.php:

Загрузите страницу в ваш браузер и на экране должна появиться следующая строка:

Object(MyClass)#1 (0) { }

Вы только что создали свой первый скрипт ООП.

Определение свойств класса

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

Чтобы добавить свойства классу MyClass, используйте такой код в вашем скрипте:

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

Echo $obj->prop1;

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

Модифицируйте скрипт в test.php, чтобы прочитать значение свойства, а не выводить информацию обо всем классе:

prop1; // Выводим свойство?>

Обновите страницу в вашем браузере, чтобы увидеть результат работы скрипта:

Свойство класса

Определение методов класса

Метод — это функция класса. Индивидуальное действие, которое может выполнить объект, определяется в классе как метод.

Например, создадим методы, которые устанавливают и читают значение свойства $prop1:

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } $obj = new MyClass; echo $obj->prop1; ?>

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

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

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } $obj = new MyClass; echo $obj->getProperty(); // получаем значение свойства $obj->setProperty("Новое свойство."); // Устанавливаем новое значение echo $obj->getProperty(); // Снова читаем значение, чтобы увидеть изменения?>

Обновляем страницу в браузере и видим следующее:

Свойство класса Новое свойство

Преимущества ООП проявляются при использовании множества объектов одного класса.

prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем два объекта $obj = new MyClass; $obj2 = new MyClass; // Получаем значения $prop1 из обоих объектов echo $obj->getProperty(); echo $obj2->getProperty(); // Устанавливаем новые значения свойств для обоих объектов $obj->setProperty("Новое значение свойства"); $obj2->setProperty("Свойство принадлежит второму объекту"); // Выводим значения $prop1 для обоих echo $obj->getProperty(); echo $obj2->getProperty(); ?>

Когда вы загрузите страницу в браузер, то увидите следующее:

Свойство класса Свойство класса Новое значение свойства Свойство принадлежит второму объекту

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

Магические методы в ООП

Чтобы сделать использование объектов проще, PHP имеет несколько магических методов. Это специальные методы, которые вызываются, когда над объектом производятся определённые действия. Таким образом разработчик может выполнить несколько общих задач относительно просто.

Использование конструкторов и деструкторов

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

Для иллюстрации концепции, добавим конструктор к классу MyClass. Он будет выводить сообщение при создании нового объекта класса:

"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->
"; ?>

Примечание — константа __CLASS__ возвращает имя класса, в котором она вызывается; это одна из магических констант PHP.

Создан объект класса "MyClass"! Свойство класса Конец файла.

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

Выведем сообщение, когда будет удаляться объект класса, с помощью магического метода:
__destruct() in MyClass:

"; } public function __destruct() { echo "Объект класса "", __CLASS__, "" удален.
"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаём новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->getProperty(); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

Обновляем страницу в браузере и получаем результат:

Создан объект класса "MyClass"! Свойство класса Конец файла. Объект класса "MyClass" удален.

При достижении конца файла PHP автоматически освобождает все ресурсы.

Для явного вызова деструктора и удаления объекта Вы можете использовать функцию unset() :


"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Получаем значение свойства $prop1 echo $obj->getProperty(); // Удаляем объект unset($obj); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

Теперь результат работы кода будет выглядеть после загрузки в браузер следующим образом:

Создан объект класса "MyClass"! Свойство класса Объект класса "MyClass" удален. Конец файла.

Преобразование в строку

Чтобы избежать ошибки, если скрипт попытается вывести MyClass как строку, используется другой магический метод __toString() .

Без использования __toString() попытка вывести объект как строку приведёт к фатальной ошибке . Попробуйте использовать функцию echo, чтобы вывести объект без применения магического метода:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "

"; ?>

Результат работы будет выглядеть следующим образом:

Создан объект класса "MyClass"! Catchable fatal error: Object of class MyClass could not be converted to string in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 40

Чтобы избежать ошибки, используйте метод __toString() :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } // Создаем новый объект $obj = new MyClass; // Выводим объект как строку echo $obj; // Удаляем объект unset($obj); // Выводим сообщение о достижении конца файла echo "Конец файла.
"; ?>

В этом случае попытка конвертировать объект в строку приведёт к вызову метода getProperty() . Загружаем скрипт в браузер и смотрим на результат работы:

Создан объект класса "MyClass"! Используем метод toString: Свойство класса Объект класса "MyClass" удален. Конец файла.

Использование наследования классов

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

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Используем новый метод echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

После загрузки скрипта в браузер получим результат:

Создан объект класса "MyClass"! Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Перегрузка унаследованных свойств и методов

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

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { echo "Новый конструктор в классе " . __CLASS__ . ".

"; } } // Создаем новый объект $newobj = new MyOtherClass; // Выводим объект как строку echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

Изменения приведут к следующему выводу при выполнении кода:

Новый конструктор в классе "MyOtherClass". Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Сохранение оригинальной функциональности при перегрузке методов

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

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } public function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); // Вызываем конструктор родительского класса echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Выводим объект как строку echo $newobj->newMethod(); // Используем метод из родительского класса echo $newobj->getProperty(); ?>

Выше приведённый код при выполнении выведет на экран сообщения из обеих конструкторов нового и родительского класса:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Из нового метода класса "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Определение области видимости свойств и методов

Для дополнительного контроля над объектами, методами и свойствами устанавливается область видимости. Таким образом контролируется как и откуда могут быть доступны свойства и методы. Существует три ключевых слова для установки области видимости: public , protected , и private . В дополнение к установке области видимости, методы и свойства могут быть объявлены как static , что позволяет получать к ним доступ без реализации класса.

Примечание — Область видимости — это новое свойство, которое было введено в PHP 5. чтобы узнать о совместимости ООП с PHP 4 , смотрите руководство по использованию PHP.

Свойства и методы public (Общие)

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

Методы и свойства protected (Защищённые)

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

Объявим метод getProperty()как protected в MyClass и попробуем получить доступ к нему вне класса:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } protected function getProperty() { return $this->prop1 . "

"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } } // Создаем новый объект $newobj = new MyOtherClass; // Пробуем вызвать защищенный метод echo $newobj->getProperty(); ?>

При попытке выполнить скрипт, будет сгенерирована следующая ошибка:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Fatal error: Call to protected method MyClass::getProperty() from context "" in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 55

Теперь, создадим новый метод в MyOtherClass для вызова метода getProperty() :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } protected function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } // Создаём новый объект $newobj = new MyOtherClass; // Вызываем защищённый метод из общего метода echo $newobj->callProtected(); ?>

При выполнении скрипта результат будет таким:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Свойство класса Объект класса "MyClass" удален.

Методы и свойства private (Частные)

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

Для демонстрации объявим метод getProperty() как private в MyClass , и попробуем вызвать метод callProtected() из
MyOtherClass :

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } private function getProperty() { return $this->prop1 . "
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } // Создаём новый объект $newobj = new MyOtherClass; // Используем метод из родительского класса echo $newobj->callProtected(); ?>

Сохраняем скрипт, обновляем страницу в браузере и получаем следующее:

Создан объект класса "MyClass"! Новый конструктор в классе "MyOtherClass". Fatal error: Call to private method MyClass::getProperty() from context "MyOtherClass" in /Applications/XAMPP/xamppfiles/htdocs/testing/test.php on line 49

Методы и свойства Static (статические)

Методы и свойства, объявленные с директивой static могут быть доступны без инициации класса. Вы просто используете имя класса, оператор разрешения видимости и имя свойства или метода.

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

Для демонстрации добавим статическое свойство $count и статический метод plusOne() к классу MyClass . Затем установим цикл do...while для вывода увеличивающего значения $count, до тех пор пока оно не станет больше 10:

"; } public function __destruct() { echo "Объект классса "", __CLASS__, "" удален.
"; } public function __toString() { echo "Используем метод toString: "; return $this->getProperty(); } public function setProperty($newval) { $this->prop1 = $newval; } private function getProperty() { return $this->prop1 . "
"; } public static function plusOne() { return "count = " . ++self::$count . ".
"; } } class MyOtherClass extends MyClass { public function __construct() { parent::__construct(); echo "Новый конструктор в классе " . __CLASS__ . ".
"; } public function newMethod() { echo "Из нового метода класса " . __CLASS__ . ".
"; } public function callProtected() { return $this->getProperty(); } } do { // Вызываем plusOne без инициации класса MyClass echo MyClass::plusOne(); } while (MyClass::$count < 10); ?>

Примечание — Для доступа к статическим свойствам знак доллара ($) должен стоять после оператора разрешения видимости.

Выполнение скрипта в браузере выдаст следующий результат:

Count = 1. count = 2. count = 3. count = 4. count = 5. count = 6. count = 7. count = 8. count = 9. count = 10.