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

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

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

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

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

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

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

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

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

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

ООП характеризуется следующими принципами ( по Алану Кею):

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

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

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

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

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

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

Определение 10.4 .

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

Что такое ООП

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

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

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

Примеры объектно-ориентированных языков:

  1. Pascal. С выходом Delphi 7 на официальном уровне стал называться Delphi. Основная область использования Object Pascal - написание прикладного ПО.
  2. C++ широко используется для разработки программного обеспечения, является одним из самых популярных языков. Применяется для создания ОС, прикладных программ, драйверов устройств, приложений, серверов, игр.
  3. Java - транслируется в байт-код, обрабатывается виртуальной машиной Java. Преимуществом такого способа выполнения является независимость от операционной системой и оборудования. Существующие семейства: Standard Edition, Enterprise Edition, Micro Edition, Card.
  4. JavaScript применяется в качестве языка сценариев для web-страниц. Синтаксис во многом напоминает Си и Java. Является реализацией Ecmascript. Сам Ecmascript используется в качестве основы для построения других таких как JScript, ActionScript.
  5. Objective-C построен на основе языка Си, а сам код Си понятен компилятору Objective-C.
  6. Perl - высокоуровневый интерпретируемый динамический язык общего назначения. Имеет богатые возможности для работы с текстом, изначально разработан именно для манипуляций с текстом. Сейчас используется в системном администрировании, разработке, сетевом программировании, биоинформатике и т. д.
  7. PHP. Аббревиатура переводится как препроцессор гипертекста. Применяется для разработки веб-приложений, в частности серверной части. С его помощью можно создавать gui-приложения с помощью пакетов WinBinder.
  8. Python - язык общего назначения, ориентирован на повышение производительности разработчика и читаемость кода. Был разработан проект Cython, с помощью которого осуществляется трансляция программ, написанных на Python в код на языке Си.

Абстракция

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

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

Известный афоризм Дэвида Уилера гласит: Все проблемы в информатике можно решить на другом уровне абстракции.

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

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

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

Существует несколько типов наследования:

  • простое;
  • множественное.

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

Наследование выглядит так:

function draw() {

return "just animal";

function eat() {

return "the animal is eating";

class Cow extends Animal {

function draw() {

Return "something that looks like a cow";

Видим, что class Cow унаследовал все методы от class Animal. Теперь, если выполнить Cow.eat(), получаем "the animal is eating", соответственно, метод draw() изменен. Cow.draw() вернет “something that looks like a cow”, а Animal.draw() вернет “just animal”.

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

Инкапсуляция ограничивает доступ компонентов к другим, связывает данные с методами для обработки. Для инкапсуляции используется спецификатор доступа private.

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

function __construct($name) {

$this->name = $name;

function getName() {

return $this->name;

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

Полиморфизм

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

В примере выше находится таблица. Мы видим class CardDesk и class GraphicalObject. У обоих есть функция под названием draw(). Она выполняет разные действия, хотя имеет одно имя.

Ad hoc полиморфизм или специальный полиморфизм использует:

  • перегрузку функций, методов;
  • приведение типов.

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

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

«Один интерфейс — много реализаций» Бьерн Страуструп.

Класс

Класс - это такой тип данных, который состоит из единого набора полей и методов.

Имеет внутренние и внешние интерфейсы для управления содержимым. При копировании через присваивание копируется интерфейс, но не данные. Разные виды взаимодействуют между собой посредством:

  • наследования;
  • ассоциации;
  • агрегации.

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

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

В Object Pascal описывается следующим образом:

ClassName = class(SuperClass)

{ использование элементов ограничивается только пределами модуля }

{ здесь указываются поля }

{ спецификатор доступа стал доступным с выходом Delphi 2007, обозначает то же, что и private }

{ элементы могут использоваться внутри ClassName или при наследовании }

{ элементы доступны всем, они отображаются в Object Inspector"e }

Здесь SuperClass - предок, от которого происходит наследование.

Для C++ создание выглядит так:

class MyClass: public Parent

MyClass(); // конструктор

~MyClass(); // деструктор

В этом примере Parent является предком, если таковой имеется. Спецификаторы private, public, protected обозначают то же самое, что в предыдущем примере на Паскале. Также мы видим конструктор, деструктор, доступные для любой части программы. У C++ все элементы по умолчанию являются private, соответственно, это можно не указывать.

Особенности реализации

В центре объектно-ориентированных языков - объект, он является частью класса. Он состоит из:

  • поля;
  • метода.

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

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

ООП-методологии

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

  • Компонентно-ориентированное программирование;
  • Прототипное программирование;
  • Классоориентированное программирование.

Компонентно-ориентированное программирование опирается на понятие компонента - такого составляющего программы, которое предназначено для повторного использования. Реализуется как множество конструкций с общим признаком, правилами, ограничениями. Подход используется в объектно-ориентированном языке Java, где компонентная ориентация реализуется посредством “JavaBeans”, написанных по одним правилам.

В прототипном программировании нет понятия класса - наследование производится за счет клонирования существующего прототипа. Это основа объектно-ориентированных языков javascript и других диалектов ecmascript, а также lua или lo. Главные особенности:

  • потомки не должны сохранять структурное подобие прототипа (в отношении класс - экземпляр это происходит именно так);
  • при копировании прототипа все методы наследуются один в один.

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

Объектно-ориентированные языки

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

ООЯ обязательно содержит набор следующих элементов:

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

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

  • конструктор, деструктор, финализаторы;
  • свойства;
  • индексаторы;
  • модификаторы доступа.

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

ЯП поддерживают больше, чем одну парадигму. Например, PHP или JavaScript поддерживают функциональное, процедурное, объектно-ориентированное программирование. Java работает с пятью парадигмами: объектно-ориентированной, обобщенной, процедурной, аспектно-ориентированной, конкурентной. C# считается одним из самых успешных примеров мультипарадигмальности. Он поддерживает те же подходы, что Java, к этому списку добавляется еще рефлексивная парадигма. Такой ЯП, как Oz, разработан для того, чтобы объединить все понятия, традиционно связанные с различными программными парадигмами.

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

Что такое объект?

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

Абстракция

Давайте теперь подумаем, как нам перейти от объекта из реального мира к объекту в программе на примере телефона. История этого средства связи превышает 100 лет и современный телефон, в отличие от своего предшественника из 19 века, представляет собой куда более сложное устройство. Когда мы пользуемся телефоном, то не задумываемся о его устройстве и процессах, происходящих внутри него. Мы просто используем функции, предоставленные разработчиками телефона - кнопки или сенсорный экран для выбора номера и совершения вызовов. Одним из первых интерфейсов телефона была рукоятка, которую нужно было вращать, чтобы сделать вызов. Разумеется, это было не очень удобно. Тем не менее, свою функцию рукоять исправно выполняла. Если посмотреть на самый современный и на самый первый телефон, можно сразу выделить самые важные детали, которые важны и для устройства конца 19-го века, и для суперсовременного смартфона. Это совершение вызова (набор номера) и приём вызова. По сути это то, что делает телефон телефоном, а не чем-то другим. Сейчас мы применили принцип в ООП - выделение наиболее важных характеристик и информации об объекте. Этот принцип называется абстракцией. Абстракцию в ООП можно также определить, как способ представления элементов задачи из реального мира в виде объектов в программе. Абстракция всегда связана с обобщением некоторой информации о свойствах предметов или объектов, поэтому главное - это отделить значимую информацию от незначимой в контексте решаемой задачи. При этом уровней абстракции может быть несколько. Попробуем применить принцип абстракции к нашим телефонам. Для начала выделим наиболее распространённые типы телефонов от самых первых и до наших дней. Например, их можно представить в виде диаграммы, приведенной на рисунке 1. Теперь с помощью абстракции мы можем выделить в этой иерархии объектов общую информацию: общий абстрактный тип объектов - телефон, общую характеристику телефона - год его создания, и общий интерфейс - все телефоны способны принимать и посылать вызовы. Вот как это выглядит на Java: public abstract class AbstractPhone { private int year; public AbstractPhone (int year) { this . year = year; } public abstract void call (int outputNumber) ; public abstract void ring (int inputNumber) ; } На основании этого абстрактного класса мы сможем создавать в программе новые типы телефонов с использованием других базовых принципов ООП Java, которые рассмотрим ниже.

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

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

Инкапсуляция и управление доступом

Допустим, при производстве на тыльной стороне телефона гравируется информация о нем: год его выпуска или логотип компании производителя. Эта информация вполне конкретно характеризует данную модель - его состояние. Можно сказать, разработчик телефона позаботился о неизменности этой информации - вряд ли кому-то придет в голову удалять гравировку. В мире Java состояние будущих объектов описывается в классе с помощью полей, а их поведение – с помощью методов. Возможность же изменения состояния и поведения осуществляется с помощью модификаторов доступа к полям и методам – private, protected, public , а также default (доступ по умолчанию). Например, мы решили, что год создания, название производителя телефона и один из методов относятся к внутренней реализации класса и не подлежат изменению другими объектами в программе. С помощью кода класс можно описать так: public class SomePhone { private int year; private String company; public SomePhone (int year, String company) { this . year = year; this . company = company; } private void openConnection () { //findComutator //openNewConnection... } public void call () { openConnection () ; System. out. println ("Вызываю номер" ) ; } public void ring () { System. out. println ("Дзынь-дзынь" ) ; } } Модификатор private делает доступными поля и методы класса только внутри данного класса. Это означает, что получить доступ к private полям из вне невозможно, как и нет возможности вызвать private методы. Сокрытие доступа к методу openConnection , оставляет нам также возможность к свободному изменению внутренней реализации этого метода, так как этот метод гарантированно не используется другими объектами и не нарушит их работу. Для работы с нашим объектом мы оставляем открытыми методы call и ring с помощью модификатора public . Предоставление открытых методов для работы с объектом также является частью механизма инкапсуляции, тат как если полностью закрыть доступ к объекту – он станет бесполезным.

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

Давайте посмотрим еще раз на диаграмму телефонов. Можно заметить, что она представляет собой иерархию, в которой модель, расположенная ниже обладает всеми признаками моделей, расположенных выше по ветке, плюс своими собственными. Например, смартфон, использует сотовую сеть для связи (обладает свойствами сотового телефона), является беспроводным и переносным (обладает свойствами беспроводного телефона) и может принимать и делать вызовы (свойствами телефона). В этом случае мы можем говорить о наследовании свойств объекта. В программировании наследование заключается в использовании уже существующих классов для описания новых. Рассмотрим пример создания класса смартфон с помощью наследования. Все беспроводные телефоны работают от аккумуляторных батарей, которые имеют определенный ресурс работы в часах. Поэтому добавим это свойство в класс беспроводных телефонов: public abstract class WirelessPhone extends AbstractPhone { private int hour; public WirelessPhone (int year, int hour) { super (year) ; this . hour = hour; } } Сотовые телефоны наследуют свойства беспроводного телефона, мы также добавили в этот класс реализацию методов call и ring: public class CellPhone extends WirelessPhone { public CellPhone (int year, int hour) { super (year, hour) ; } @Override public void call (int outputNumber) { System. out. println ("Вызываю номер " + outputNumber) ; } @Override public void ring (int inputNumber) { System. out. println ("Вам звонит абонент " + inputNumber) ; } } И, наконец, класс смартфон, который в отличие от классических сотовых телефонов имеет полноценную операционную систему. В смартфон можно добавлять новые программы, поддерживаемые данной операционной системой, расширяя, таким образом, его функциональность. С помощью кода класс можно описать так: public class Smartphone extends CellPhone { private String operationSystem; public Smartphone (int year, int hour, String operationSystem) { super (year, hour) ; this . operationSystem = operationSystem; } public void install (String program) { System. out. println ("Устанавливаю " + program + "для" + operationSystem) ; } } Как видите, для описания класса Smartphone мы создали совсем немного нового кода, но получили новый класс с новой функциональностью. Использование этого принципа ООП java позволяет значительно уменьшить объем кода, а значит, и облегчить работу программисту.

Полиморфизм

Если мы посмотрим на все модели телефонов, то, несмотря на различия во внешнем облике и устройстве моделей, мы можем выделить у них некое общее поведение – все они могут принимать и совершать звонки и имеют достаточно понятный и простой набор кнопок управления. Применяя известный нам уже один из основных принципов ООП абстракцию в терминах программирования можно сказать, что объект телефон имеет один общий интерфейс. Поэтому пользователи телефонов могут вполне комфортно пользоваться различными моделями, используя одни и те же кнопки управления (механические или сенсорные), не вдаваясь в технические тонкости устройства. Так, вы постоянно пользуетесь сотовым телефоном, и без труда сможете совершить звонок с его стационарного собрата. Принцип в ООП, когда программа может использовать объекты с одинаковым интерфейсом без информации о внутреннем устройстве объекта, называется полиморфизмом . Давайте представим, что нам в программе нужно описать пользователя, который может пользоваться любыми моделями телефона, чтобы позвонить другому пользователю. Вот как можно это сделать: public class User { private String name; public User (String name) { this . name = name; } public void callAnotherUser (int number, AbstractPhone phone) { // вот он полиморфизм - использование в коде абстактного типа AbstractPhone phone! phone. call (number) ; } } } Теперь опишем различные модели телефонов. Одна из первых моделей телефонов: public class ThomasEdisonPhone extends AbstractPhone { public ThomasEdisonPhone (int year) { super (year) ; } @Override public void call (int outputNumber) { System. out. println ("Вращайте ручку" ) ; System. out. println ("Сообщите номер абонента, сэр" ) ; } @Override public void ring (int inputNumber) { System. out. println ("Телефон звонит" ) ; } } Обычный стационарный телефон: public class Phone extends AbstractPhone { public Phone (int year) { super (year) ; } @Override public void call (int outputNumber) { System. out. println ("Вызываю номер" + outputNumber) ; } @Override public void ring (int inputNumber) { System. out. println ("Телефон звонит" ) ; } } И, наконец, крутой видеотелефон: public class VideoPhone extends AbstractPhone { public VideoPhone (int year) { super (year) ; } @Override public void call (int outputNumber) { System. out. println ("Подключаю видеоканал для абонента " + outputNumber ) ; } @Override public void ring (int inputNumber) { System. out. println ("У вас входящий видеовызов..." + inputNumber) ; } } Создадим объекты в методе main() и протестируем метод callAnotherUser: AbstractPhone firstPhone = new ThomasEdisonPhone (1879 ) ; AbstractPhone phone = new Phone (1984 ) ; AbstractPhone videoPhone= new VideoPhone (2018 ) ; User user = new User ("Андрей" ) ; user. callAnotherUser (224466 , firstPhone) ; // Вращайте ручку //Сообщите номер абонента, сэр user. callAnotherUser (224466 , phone) ; //Вызываю номер 224466 user. callAnotherUser (224466 , videoPhone) ; //Подключаю видеоканал для абонента 224466 Используя вызов одного и того же метода объекта user , мы получили различные результаты. Выбор конкретной реализации метода call внутри метода callAnotherUser производился динамически на основании конкретного типа вызывающего его объекта в процессе выполнения программы. В этом и заключается основное преимущество полиморфизма – выбор реализации в процессе выполнения программы. В примерах классов телефонов, приведенных выше, мы использовали переопределение методов – прием, при котором изменяется реализация метода, определенная в базовом классе, без изменения сигнатуры метода. По сути это является заменой метода, и именно новый метод, определенный в подклассе, вызывается при выполнении программы. Обычно, при переопределении метода, используется аннотация @Override , которая подсказывает компилятору о необходимости проверить сигнатуры переопределяемого и переопределяющего методов. В итоге , чтобы стиль вашей программы соответствовал концепции ООП и принципам ООП java следуйте следующим советам:
  • выделяйте главные характеристики объекта;
  • выделяйте общие свойства и поведение и используйте наследование при создании объектов;
  • используйте абстрактные типы для описания объектов;
  • старайтесь всегда скрывать методы и поля, относящиеся к внутренней реализации класса.

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

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

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


Об объектах и классах

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

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

Одним из самых распространенных объектно-ориентированных языков программирования является язык 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

1 year ago | 13.4K

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

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

Введение в ООП

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

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

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

Процедурное программирование:

$value = "Hi!"; echo $value;

ООП:

class Human { private $words; public function setWords($words) { $this->words = $words; } public function getWords() { return $this->words; } public function sayIt() { return $this->getWords(); } } $human = new Human(); $human->setWords("Hi!"); echo $human->sayIt();

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

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

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

Что из себя представляет КЛАСС

Здесь все достаточно просто - это методы, а также, поля программы. В качестве примера можно рассмотреть Human:

Class Human { private $words; public function setWords($words) { $this->words = $words; } public function getWords() { return $this->words; } public function sayIt() { return $this->getWords(); } }

Здесь все достаточно просто, Human является именем класса, $words представляет собой переменную (поле), ну а setWords, getWords(), sayIt(), являются методами.

Несколько базовых принципов ООП

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

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

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

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

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

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

Приведем в качестве примера инкапсуляции следующее:

Class Human { private $words; private $sex; public function setSex($sex) { if($sex == "male") { $this->sex = "male"; } else if($sex == "female") { $this->sex = $sex; } else { echo "Error. Set only male or female sex"; } } public function setWords($words) { $this->words = $words; } public function getWords() { return $this->words; } public function sayIt() { return $this->getWords(); } } $human = new Human(); $human->setSex("male"); $human->setWords("Hi!"); echo $human->sayIt();

Здесь показан Human, в данном классе мы добавили “sex” (пол), которое сделали приватным - это не позволит внешним пользователям получить к нему доступ.

Вот как будет выглядеть попытка получения доступа к данному поля вне самого класса:

$human->sex = "11"; Fatal error: Cannot access private property Human::$sex

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

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

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

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

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

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

Как это работает? Существуют родительские классы с базовым функционалом - при создании нового класса, не потребуется создание новых базовых навыков, он изначально унаследует "базу" родительского класса. Это существенно упрощает работу программистам. Есть слово "extends", которое обозначает наследование, сейчас покажем вам определенный пример:

/* Родительский класс Human */ class Human { private $name; /* Конструктор (в нем мы задаем поле $name при создании экземпляра класса) */ public function __construct($name) { $this->name = $name; } /* Метод say(). Предполагаем, что Human изначально может говорить */ public function say() { echo "Меня зовут ".$this->name." и "; } } /* Класс Мужчина. Ключевым словом extends мы наследуем родителя Human */ class Man extends Human { public function beard() { echo "у меня растет борода"; } } /* Класс Женщина. Ключевым словом extends мы наследуем родителя Human */ class Women extends Human { public function bearChildren() { echo "я рожаю детей"; } } /* Создаем экземпляр класса Man и вызываем методы. */ $man = new Man("Sergey"); $man->say(); $man->beard(); /* Создаем экземпляр класса Women и вызываем методы. */ $women = new Women("Maria"); $women->say(); $women->bearChildren();

Что же мы увидим в результате:

Меня зовут Sergey и у меня растет борода Меня зовут Maria и я рожаю детей

Если рассматривать созданные классы - они оба обладают базовым набором навыков, но есть свои отличия - "Мужчина" отращивает бороду, "Женщина" рожает ребенка.

Метод под названием __construct - это конструктор класса.

Полиморфизм

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

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

Ну как? Теперь проще понять разное поведение одного метода в ООП?

Пример полиморфизма:

/* Это интерфейс Say */ interface Say { public function say(); } /* Это абстрактный класс Human имплементирующий интерфейс Say */ abstract class Human implements Say{ private $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } } /* Класс Man наследуют класс Human и обязан реализовать метод say() */ class Man extends Human { public function __construct($name) { parent::__construct($name); } public function beard() { echo "у меня растет борода"; } public function say() { echo "У меня мужской голос, меня зовут ".$this->getName()." и "; } } /* Класс Women наследуют класс Human и обязан реализовать метод say() */ class Women extends Human { public function __construct($name) { parent::__construct($name); } public function bearChildren() { echo "я рожаю детей"; } public function say() { echo "У меня женский голос, меня зовут ".$this->getName()." и "; } } $man = new Man("Sergey"); $man->say(); $man->beard(); $women = new Women("Maria"); $women->say(); $women->bearChildren(); ?>

Результат:

У меня мужской голос, меня зовут Sergey и у меня растет борода У меня женский голос, меня зовут Maria и я рожаю детей

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

Обратите внимание на разную реализацию метода say() в классах Man, а также, Women - Это называется полиморфизм.

Особенности Интерфейса

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

Рассмотрим прошлый пример:

/* Это интерфейс Say */ interface Say { public function say(); }

Данный интерфейс необходимо имплементировать в абстрактный класс Human. Сделать это достаточно просто - находим название класса, и сразу же после него добавляем "implements".

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

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

Пример того, чего мы не сможем сделать:

$human = new Human("name");

В любом случае будем получать ошибку.

Есть возможность наследования Абстрактного класса. Еще один пример Абстрактного класса:

/* Это абстрактный класс Human имплементирующий интерфейс Say */ abstract class Human implements Say{ private $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } }

Выводы

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

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

Мы привели достаточно простые примеры - это отличная возможность для любого новичка в сегменте программирования! Надеемся, что помогли вам понять особенности ООП, изучить наиболее важные принципы, чтобы в дальнейшем совершенствовать свои умения. Несколько будущих статей будут направлены на то, чтобы разобраться с важными ключевыми словами public, private, protected, static. Увидеть все особенности на весьма простых примерах.