Компьютеры Windows Интернет

Что такое полиморфизм в программировании. Объектно-ориентированное программирование (ООП): полиморфизм. Теперь об использовании

Привет! Это статья об одном из принципов ООП - полиморфизм.

Что такое полиморфизм

Определение полиморфизма звучит устрашающе 🙂

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

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

Полиморфизм , если перевести, - это значит "много форм". Например, актер в театре может примерять на себя много ролей - или принимать "много форм".

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

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

Как проявляется полиморфизм

Дело в том, что если бы в Java не было принципа полиморфизма , компилятор бы интерпретировал это как ошибку:

Как видите, методы на картинке отличаются значениями, которые они принимают:

  • первый принимает int
  • а второй принимает String

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

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

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

Теперь Вы можете понять, почему часто этот принцип описывают фразой:

Один интерфейс - много методов

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

Перегрузка методов

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

Переопределение методов родителя

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

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

Пример

Представим, что у нас есть такая структура:

Вверху иерархии классов стоит класс Animal . Его наследуют три класса - Cat , Dog и Cow .

У класса "Animal" есть метод "голос" (voice). Этот метод выводит на экран сообщение "Голос". Естественно, ни собака, ни кошка не говорят "Голос" 🙂 Они гавкают и мяукают. Соответственно, Вам нужно задать другой метод для классов Cat , Dog и Cow - чтобы кошка мяукала, собака гавкала, а корова говорила "Муу".

Поэтому, в классах-наследниках мы переопределяем метод voice() , чтобы мы в консоли получали "Мяу", "Гав" и "Муу".

  • Обратите внимание: перед методом, который мы переопределяем, пишем "@Override ". Это дает понять компилятору, что мы хотим переопределить метод.

Так что же такое полиморфизм

Тем не менее, полиморфизм - это принцип. Все реальные примеры, которые мы приведодили выше - это только способы реализации полиморфизма.

Давайте снова посмотрим на определение, которое мы давали в начале статьи:

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

Выглядит понятнее, правда? Мы показали, как можно:

  • создавать "одноименные методы" в одном классе ("перегрузка методов")
  • или изменить поведение методов родительского класса ("переопределение методов").

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

Надеемся, наша статья была Вам полезна. Записаться на наши курсы по Java можно у нас на .

s_a_p 20 августа 2008 в 19:09

Полиморфизм для начинающих

  • PHP

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

Постановка задачи

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

Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (get_news, get_announcements, get_articles) — это уже совсем неграмотно. Тут нам и поможет полиморфизм.

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

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

abstract class Publication
{
// таблица, в которой хранятся данные по элементу
protected $table ;

// свойства элемента нам неизвестны
protected $properties = array();

// конструктор

{
// обратите внимание, мы не знаем, из какой таблицы нам нужно получить данные
$result = mysql_query ("SELECT * FROM `" . $this -> table . "` WHERE `id`="" . $id . "" LIMIT 1" );
// какие мы получили данные, мы тоже не знаем
$this -> properties = mysql_fetch_assoc ($result );
}

// метод, одинаковый для любого типа публикаций, возвращает значение свойства
public function get_property ($name )
{
if (isset($this -> properties [ $name ]))
return $this -> properties [ $name ];

Return false ;
}

// метод, одинаковый для любого типа публикаций, устанавливает значение свойства
public function set_property ($name , $value )
{
if (!isset($this -> properties [ $name ]))
return false ;

$this -> properties [ $name ] = $value ;

Return $value ;
}

// а этот метод должен напечатать публикацию, но мы не знаем, как именно это сделать, и потому объявляем его абстрактным
abstract public function do_print ();
}

Производные классы

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

class News extends Publication
{
// конструктор класса новостей, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по новостям
$this -> table = "news_table" ;
parent :: __construct ($id );
}

Public function do_print ()
{
echo $this -> properties [ "title" ];
echo "

" ;
echo $this -> properties [ "text" ];
echo "
Источник: " . $this -> properties [ "source" ];
}
}

Class Announcement extends Publication
{
// конструктор класса объявлений, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по объявлениям
$this -> table = "announcements_table" ;
// вызываем конструктор родительского класса
parent :: __construct ($id );
}

// переопределяем абстрактный метод печати
public function do_print ()
{
echo $this -> properties [ "title" ];
echo "
Внимание! Объявление действительно до "
. $this -> properties [ "end_date" ];
echo "

" . $this -> properties [ "text" ];
}
}

Class Article extends Publication
{
// конструктор класса статей, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по статьям
$this -> table = "articles_table" ;
// вызываем конструктор родительского класса
parent :: __construct ($id );
}

// переопределяем абстрактный метод печати
public function do_print ()
{
echo $this -> properties [ "title" ];
echo "

" ;
echo $this -> properties [ "text" ];
echo "
" . $this -> properties [ "author" ];
}
}

Теперь об использовании

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

// наполняем массив публикаций объектами, производными от Publication
$publications = new News ($news_id );
$publications = new Announcement ($announcement_id );
$publications = new Article ($article_id );

Foreach ($publications as $publication ) {
// если мы работаем с наследниками Publication
if ($publication instanceof Publication ) {
// то печатаем данные
$publication -> do_print ();
} else {
// исключение или обработка ошибки
}
}

Вот и все. Легким движением руки брюки превращаются в элегантные шорты:-).

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

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

  • Методы, которые требуют переопределения, называются абстрактными. Логично, что если класс содержит хотя бы один абстрактный метод, то он тоже является абстрактным.
  • Очевидно, что обьект абстрактного класса невозможно создать, иначе он не был бы абстрактным.
  • Производный класс имеет свойства и методы, принадлежащие базовому классу, и, кроме того, может иметь собственные методы и свойства.
  • Метод, переопределяемый в производном классе, называется виртуальным. В базовом абстрактном классе об этом методе нет никакой информации.
  • Суть абстрагирования в том, чтобы определять метод в том месте, где есть наиболее полная информация о том, как он должен работать.
UPD: по поводу sql-inj и нарушения MVC — господа, это просто пример, причем пример по полиморфизму, в котором я не считаю нужным уделять значения этим вещам. Это тема для совсем других статей.

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

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

Уровень и квалификация

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

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

Формы полиморфизма ООП, идеи инкапсуляции кода и наследование свойств (методов) лежат в сфере программирования, но никак не в сфере решаемой задачи.

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

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

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

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

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

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

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

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

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

Популярные определения полиморфизма

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

  1. Полиморфизм в программировании - это способность предоставлять один и тот же интерфейс для различных базовых форм (типов данных).
  2. Полиморфизм - возможность объектов иметь различную реализацию.
  3. Полиморфизмом называется способность функции...
  4. Классика (от создателя С/С++): «один интерфейс - много реализаций».
  5. Параметрический полиморфизм подразумевает...
  6. Полиморфизм - положение теории типов...
  7. Абстракция невозможна без инкапсуляции и наследования, как невозможен полиморфизм без наследования...

Можно согласиться, что всё это относится к одному и тому же: но форма выражения мысли, сущность и содержание - не подобны. Но что-то общее всё же есть.

Сущность: разработчик - заказчик

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

Такой круговорот процесса решения задачи наводит на мысль, что здесь явно совмещены две совершенно разные сущности:

  • компьютер не может сам решить задачу;
  • нужна программа, чтобы компьютер мог «понять» и «решить» задачу.

Задача - сфера компетенции заказчика, программа - это алгоритм «адаптации» задачи к возможностям компьютера - сфера компетенции программиста. Роль последнего заключается в «адаптации» компьютера к требованиям задачи, а это лишнее!

Предлагает абстрагироваться . Есть объекты - это сфера заказчика; есть реализация объектов - это сфера программиста. Никакой «технологической» связи между заказчиком и разработчиком. Идея кардинальная, не реализованная по сей день, но что-то уже стабильно работает.

Окна, кнопки и другие объекты

История the Air Art Technology, Object Magazine, Turbo Vision, Graph Vision - это уже история. Мало кто помнит эти реализации ООП, они практически не используются и забыты, но оконный интерфейс Windows знаком миллионам пользователей, а объекты в средах PHP, JavaScript и других языках интернет-технологий применяются сотнями тысяч разработчиков кода, о них знают миллионы посетителей веб-ресурсов.

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

Концепция, положенная в основу продуктов типа the Air Art Technology и Object Magazine, существенно отличалась. Здесь абстрактный объект был самым первым предком информационной структуры, инкапсулировал на абстрактном уровне код обработки информации. Объекты окон, кнопок, элементов визуального оформления здесь были вторичны.

В первом варианте (Windows & etc.) парадигма ООП: инкапсуляция, наследование, полиморфизм обозначалась на уровне абстрактного предка, а реализация кода формировалась на уровне каждого конкретного потомка по ветке наследования согласно нужным структуре и содержанию.

Во втором варианте (the Air Art Technology и Object Magazine) важен уровень абстрактного объекта. Что будет у конкретного потомка - не суть, главное, чтобы его ветка наследования удовлетворяла требованиям всех родителей вниз до корневой абстракции.

Объект и система объектов: алгоритм

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

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

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

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

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

Объект и система объектов: задача и решение

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

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

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

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

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

Традиционное и объектное программирование

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

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

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

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

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

Что в основе: объект или система

Абстракция, как основное концептуальное положение ООП, вне зависимости от того, где находится зона ответственности (реализация) объекта - на уровне первого абстрактного объекта или на уровне конкретного потомка, - оставляет открытым вопрос: с чего всё начинать, с объекта или с системы?

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

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

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

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

История процесса решения задачи

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

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

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

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

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

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

Реальный полиморфизм ООП, пример

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

Не так давно была разработана библиотека PHPOffice/PHPWord, но для того чтобы использовать её возможности, практически всегда приходится создавать собственную систему объектов. Например, простой файл *.docx:

представляет собой zip-архив множества файлов и папок в формате Office Open XML (OpenXML, OOXML). Каждый файл записан в тегами XML, причём при добавлении, изменении и удалении букв, слов, таблиц, списков и пр. элементов содержимое файлов начинает представлять собой последовательность тегов, которые не всегда содержат полные элементы, часто один элемент записывается множеством тегов.

Если представить этот файл в виде последовательности тегов, получится интересная картинка:

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

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

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

Состояние области ООП

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

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

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

Перспективы объектной идеи

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

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

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

Полиморфизм (программирование)

Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс , множество реализаций».

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

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

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

Примеры

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

В функциональных языках

В Haskell существует два вида полиморфизма - параметрический (чистый) и специальный, (на основе классов ). Специальный называют еще ad hoc (от лат. ad hoc - специально). Их можно отличить следующим образом:

Параметрический полиморфизм

Специальный полиморфизм

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

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

Неявная типизация

В некоторых языках программирования (например, в Python и Ruby) применяется так называемая утиная типизация (другие названия: латентная, неявная), которая представляет собой разновидность сигнатурного полиморфизма. Таким образом, например, в языке Python полиморфизм не обязательно связан с наследованием.

Формы полиморфизма

Статический и динамический полиморфизм

(упоминается в классической книге Саттера и Александреску, которая является источником).

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

В одном случае конкретный смысл фрагмента зависит от того, в каком окружении код был построен. Это т.н. статический полиморфизм. Перегрузка функций, шаблоны в Си++ реализуют именно статический полиморфизм. Если в коде шаблонного класса вызвана, например, std::sort, то реальный смысл вызова зависит от того, для каких именно типовых параметров будет развернут данный шаблон - вызовется одна из std::sort.

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

Полиморфизм включения

Этот полиморфизм называют чистым полиморфизмом. Применяя такую форму полиморфизма, родственные объекты можно использовать обобщенно. С помощью замещения и полиморфизма включения можно написать один метод для работы со всеми типами объектов TPerson. Используя полиморфизм включения и замещения можно работать с любым объектом, который проходит тест «is-A». Полиморфизм включения упрощает работу по добавлению к программе новых подтипов, так как не нужно добавлять конкретный метод для каждого нового типа, можно использовать уже существующий, только изменив в нем поведение системы. С помощью полиморфизма можно повторно использовать базовый класс; использовать любого потомка или методы, которые использует базовый класс.

Параметрический полиморфизм

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

Параметрические методы

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

Параметрические типы

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

Полиморфизм переопределения

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

Полиморфизм-перегрузка

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

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

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

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

В соответствии с UML ( Unified Modelling Language - унифицированный язык моделирования ), класс имеет следующее графическое представление .

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

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

  • конструктор (constructor) - выполняется при создании объектов ;
  • деструктор ( destructor ) - выполняется при уничтожении объектов .

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

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

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

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

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

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

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

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

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

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

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

В качестве примера можно рассмотреть задачу, в которой необходимо реализовать классы "Легковой автомобиль" и "Грузовой автомобиль". Очевидно, эти два класса имеют общую функциональность. Так, оба они имеют 4 колеса, двигатель, могут перемещаться и т.д. Всеми этими свойствами обладает любой автомобиль, независимо от того, грузовой он или легковой, 5- или 12-местный. Разумно вынести эти общие свойства и функциональность в отдельный класс , например, "Автомобиль" и наследовать от него классы "Легковой автомобиль" и "Грузовой автомобиль", чтобы избежать повторного написания одного и того же кода в разных классах .


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

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

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

Все животные в зоомагазине являются наследниками класса "Животное", а также наследниками класса "Товар". Т.е. все они имеют возраст, нуждаются в пище и воде и в то же время имеют цену и могут быть проданы.

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

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

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

Полиморфизм

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

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

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

... //создание пустого массива, который может // содержать объекты Point с максимальным // объемом 1000 Point p = new Point; Line l = new Line; Circle c = new Circle; Box b = new Box; ... // предположим, в этом месте происходит // заполнение всех массивов соответствующими // объектами... for(int i = 0; i < p.length;i++) { //цикл с перебором всех ячеек массива. //вызов метода draw() в случае, // если ячейка не пустая. if(p[i]!=null) p[i].draw(); } for(int i = 0; i < l.length;i++) { if(l[i]!=null) l[i].draw(); } for(int i = 0; i < c.length;i++) { if(c[i]!=null) c[i].draw(); } for(int i = 0; i < b.length;i++) { if(b[i]!=null) b[i].draw(); } ...

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

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