Что такое полиморфизм приведите пример демонстрирующий необходимость определения полиморфного метода
NEWOBJ.ru → Введение в ООП с примерами на C# →
3.5. Полиморфизм
§ 42. Определение. В предыдущих разделах, обсуждая наследование, абстрактные и виртуальные методы и интерфейсы, мы уже неоднократно сталкивались с полиморфизмом, не называя его явно. Вспомним следующий пример.
Полиморфизм (polymorphism) – возможность одной и той же переменной в различные моменты выполнения программы обозначать объекты различных типов (классов), относящихся к одному базовому типу (классу или интерфейсу).
Сам термин «полиморфизм» составлен из греческих слов πολύς, много, и μορφή, форма, и позаимствован из естественных наук.
По большому счету, полиморфизм – основной выигрыш от использования иерархических типов данных. Практическая значимость полиморфизма уже подробно анализировалась нами в предыдущих главах при рассмотрении механизмов наследования, приведения переменных производных классов к базовым, виртуальных и абстрактных методов, интерфейсов. Главное при этом – возможность писать более лаконичный и обобщенный код, опираясь на базовые типы, и, тем самым, повышать уровень абстракции и снижать сложность программы. Читатель может самостоятельно вернуться к материалу глав 3.1 – 3.4 и показать, где именно шла речь о полиморфизме и какие именно мы получали практические преимущества от его использования.
В заключение отметим, что в более широком значении, вне контекста ООП, выделяют три типа полиморфизма. Первый тип, который мы рассматриваем в настоящем параграфе, называют полиморфизмом подтипов, или «подтипизацией» ( subtyping ). Другой тип полиморфизма – параметрический полиморфизм, используется в рамках обобщённого программирования. Эта тема будет обзорно рассмотрена в следующей главе. И к третьему типу – ad hoc полиморфизму – относят перегрузку методов, исходя из логики, что одно и то же имя метода в зависимости от параметров может обозначать разные реализации.
§ 43. Принципы качественного проектирования иерархических типов. Объектно-ориентированный язык, как и любой другой язык программирования – инструмент в руках программиста, который можно применять лучше или хуже. В главе 2.6 мы говорили, что не всякое разбиение программы на части, в частности, на классы, будет удачным. Можно сказать (мы уже формулировали ранее эту идею), что основная цель, для достижения которой формулируются различные принципы (правила) качественного проектирования (design principles) заключается в том, чтобы любой фрагмент кода зависел от другого кода тогда и только тогда, когда это абсолютно необходимо (синтаксически и семантически) для решаемой задачи. Мы рассматривали несколько ключевых правил без учета специфики наследования классов. Рассмотрим теперь два важных правила, относящихся к наследованию.
Производные классы не должны сужать возможности базовых классов или менять семантику состояния и поведения базовых классов; или, другая формулировка: в любой ситуации, где используется переменная базового типа, должно быть возможно безопасно, то есть без каких-либо изменений в работе программы, заменить ее или присвоить ей экземпляр любого производного класса.
Другой принцип проектирования, также имеющий непосредственное отношение к наследованию и полиморфизму, мы уже рассматривали в предыдущем разделе – принцип инверсии зависимости (dependency inversion).
Необходимо всегда использовать наиболее абстрактный (базовый) класс, а не наиболее конкретный.
Мы руководствовались этим правилом на протяжении всех предыдущих параграфов, например, когда рассматривали задачу с модульным тестированием. Читатель уже сам может объяснить это правило – его преимущества мы показывали, обсуждая соответствующие примеры. Прежде всего, это снижение взаимозависимостей частей программы (так как наш код зависит от меньшей части другого кода, чем если бы мы использовали производные классы) и повышение абстракции (так как мы можем принимать во внимание меньше частностей). Так, руководствуясь этим правилом, при объявлении параметров методов следует всегда задаваться вопросом – действительно ли нам нужен этот класс или мы можем обойтись его базовым классом или реализуемым им интерфейсом?
Нарушение правил проектирования существенно обесценивает преимущества ООП. Сегодня сформулировано множество таких правил, более общих, более частных, некоторые из них так или иначе пересекаются. Здесь мы ограничимся указанными двумя принципами, связанными с вопросом иерархических типов данных. Для дальнейшего изучения порекомендуем классическую книгу Р. Мартина «Чистая архитектура» [Мартин 11].
Вопросы и задания
Что такое полиморфизм?
Что такое «динамическое связывание»? Что с чем связывается? Почему при использовании полиморфизма мы не можем применять статическое связывание?
Вернитесь к главам 3.2, 3.3 и 3.4 и укажите, где именно в них шла речь о полиморфизме и какие именно практические преимущества мы получаем в каждом из случаев.
Можно ли написать программу, используя наследование, механизмы виртуальных и абстрактных методов, но не применяя полиморфизм?
Полиморфизм перегрузки методов (ad hoc) – это полиморфизм динамический (с динамическим связыванием) или статический (со статическим связыванием)?
Охарактеризуйте «проблему квадрата-прямоугольника». Сформулируйте и охарактеризуйте принцип подстановки Лисков. Приведите примеры.
* Сформулируйте и охарактеризуйте принцип инверсии зависимости. Приведите примеры.
54. Не следует путать статическое/динамическое связывание и статическую/динамическую типизацию. В случае статической типизации на этапе компиляции известен тип каждой переменной и, соответственно, перечень методов, которые поддерживаются этим типом. Однако конкретная реализация метода, которая должна быть вызвана, может быть и неизвестна. То есть статическая система типов (типизация) может поддерживать как статическое, так и динамическое связывание.
55. Или ромба-прямоугольника, или круга-эллипса. Формулировки задач идентичны, только используются соответственно другие фигуры.
Полиморфизм в Python
В этой статье мы изучим полиморфизм, разные типы полиморфизма и рассмотрим на примерах как мы можем реализовать полиморфизм в Python.
Что такое полиморфизм?
В буквальном значении полиморфизм означает множество форм.
Полиморфизм — очень важная идея в программировании. Она заключается в использовании единственной сущности(метод, оператор или объект) для представления различных типов в различных сценариях использования.
Давайте посмотрим на пример:
Пример 1: полиморфизм оператора сложения
Мы знаем, что оператор + часто используется в программах на Python. Но он не имеет единственного использования.
Для целочисленного типа данных оператор + используется чтобы сложить операнды.
Подобным образом оператор + для строк используется для конкатенации.
Здесь мы можем увидеть единственный оператор + выполняющий разные операции для различных типов данных. Это один из самых простых примеров полиморфизма в Python.
Полиморфизм функций
В Python есть некоторые функции, которые могут принимать аргументы разных типов.
Пример 2: полиморфизм на примере функции len()
Вывод:
Полиморфизм функции len()
Полиморфизм в классах
Полиморфизм — очень важная идея в объектно-ориентированном программировании.
Чтобы узнать больше об ООП в Python, посетите эту статью: Python Object-Oriented Programming.
Мы можем использовать идею полиморфизма для методов класса, так как разные классы в Python могут иметь методы с одинаковым именем.
Позже мы сможем обобщить вызов этих методов, игнорируя объект, с которым мы работаем. Давайте взглянем на пример:
Пример 3: полиморфизм в методах класса
Вывод:
Полиморфизм и наследование
Как и в других языках программирования, в Python дочерние классы могут наследовать методы и атрибуты родительского класса. Мы можем переопределить некоторые методы и атрибуты специально для того, чтобы они соответствовали дочернему классу, и это поведение нам известно как переопределение метода(method overriding).
Полиморфизм позволяет нам иметь доступ к этим переопределённым методам и атрибутам, которые имеют то же самое имя, что и в родительском классе.
Давайте рассмотрим пример:
Пример 4: переопределение метода
Вывод:
Благодаря полиморфизму интерпретатор питона автоматически распознаёт, что метод fact() для объекта a (класса Square ) переопределён. И использует тот, который определён в дочернем классе.
С другой стороны, так как метод fact() для объекта b не переопределён, то используется метод с таким именем из родительского класса( Shape ).
Полиморфизм на примере дочерних и родительских классов в питоне
Заметьте, что перегрузка методов(method overloading) — создание методов с одним и тем же именем, но с разными типами аргументов не поддерживается в питоне.
Полиморфизм для начинающих
Постановка задачи
Предположим, на сайте нужны три вида публикаций — новости, объявления и статьи. В чем-то они похожи — у всех них есть заголовок и текст, у новостей и объявлений есть дата. В чем-то они разные — у статей есть авторы, у новостей — источники, а у объявлений — дата, после которой оно становится не актуальным.
Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (get_news, get_announcements, get_articles) — это уже совсем неграмотно. Тут нам и поможет полиморфизм.
Абстрактный класс
Грубо говоря, это класс-шаблон. Он реализует функциональность только на том уровне, на котором она известна на данный момент. Производные же классы ее дополняют. Но, пора перейти от теории к практике. Сразу оговорюсь, рассматривается примитивный пример с минимальной функциональностью. Все объяснения — в комментариях в коде.
// а этот метод должен напечатать публикацию, но мы не знаем, как именно это сделать, и потому объявляем его абстрактным
abstract public function do_print ();
>
Производные классы
Теперь можно перейти к созданию производных классов, которые и реализуют недостающую функциональность.
Теперь об использовании
Суть в том, что один и тот же код используется для обьектов разных классов.
Вот и все. Легким движением руки брюки превращаются в элегантные шорты :-).
Основная выгода полиморфизма — легкость, с которой можно создавать новые классы, «ведущие себя» аналогично родственным, что, в свою очередь, позволяет достигнуть расширяемости и модифицируемости. В статье показан всего лишь примитивный пример, но даже в нем видно, насколько использование абстракций может облегчить разработку. Мы можем работать с новостями точно так, как с объявлениями или статьями, при этом нам даже не обязательно знать, с чем именно мы работаем! В реальных, намного более сложных приложениях, эта выгода еще ощутимей.
Немного теории
ООП. Часть 4. Полиморфизм, перегрузка методов и операторов
C# позволяет использовать один метод для разных типов данных и даже переопределить логику операторов. Разбираемся в перегрузках.
Полиморфизм (от греч. poly — много и morphe — форма) — один из главных столпов объектно-ориентированного программирования. Его суть заключается в том, что один фрагмент кода может работать с разными типами данных.
В C# это реализуется с помощью перегрузок (overloading).
Все статьи про ООП
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Перегрузка методов
C# — строго типизированный язык. Это значит, что вы не можете поместить строку в переменную типа int — сначала нужно провести преобразование. Так же и в метод нельзя передать параметр типа float, если при объявлении метода был указан тип double.
Однако если вы экспериментировали с методом WriteLine() класса Console, то могли заметить, что в него можно передавать аргументы разных типов:
Кажется, что нарушена типизация, но компилятор не выдаёт ошибку. Вместо этого всё успешно выводится на экран:
Так происходит потому, что у метода WriteLine() есть перегрузки — методы с таким же названием, но принимающие другие аргументы:
Когда вы вызовете метод Sum(), компилятор по переданным аргументам узнает, какую из его перегрузок вы имели в виду — так же, как это происходит с методом WriteLine().
При этом стоит учитывать, что значение имеют только типы и количество передаваемых аргументов. Например, можно написать такие перегрузки:
У этих методов одинаковые параметры, но разный возвращаемый тип. Попытка скомпилировать такой код приведёт к ошибке — так же, как и создание перегрузки с такими же аргументами, но с другими названиями:
Перегрузка конструкторов
То же самое можно сделать и с конструкторами классов:
Альтернатива этому решению — указать значения для аргументов по умолчанию:
Несмотря на, то что здесь меньше кода, на мой взгляд, это может запутать. Потому что придётся каждый раз заполнять все значения, даже если нужен только один аргумент из конца списка. Перегрузка же позволяет определить и порядок параметров (если они разных типов).
Перегрузка операторов
Перегрузить можно даже операторы, то есть:
Так как использоваться этот оператор должен без объявления экземпляра класса (item1 + item2, а не item1 item1.+ item2), то указываются модификаторы public static.
Например, мы хотим улучшать предметы в играх. Во многих MMO 1 популярна механика, когда один предмет улучшается за счёт другого. Мы можем сделать это с помощью перегрузки оператора сложения:
Теперь при сложении двух объектов класса Item мы будем получать третий объект с улучшенными параметрами. Вот пример использования такого оператора:
В результате в консоль будет выведено следующее:
1) MMO (англ. Massively Multiplayer Online Game, MMO, MMOG)
Массовая многопользовательская онлайн-игра
Перегрузка операторов преобразования типов
Хотя типизация в C# строгая, типы можно преобразовывать. Например, мы можем конвертировать число типа float в число типа int:
С помощью перегрузки операторов преобразования типов мы можем прописать любую логику для конвертации объектов. Для наглядности создадим класс Hero:
В этом классе хранятся данные о персонаже. В MMO часто можно увидеть такой параметр, как мощь — это сумма всех характеристик героя или предмета. Например, её можно посчитать по следующей формуле:
Мощь = (сила + ловкость + интеллект) * уровень.
Мы можем использовать преобразование типов, чтобы автоматически переводить объект в его мощь. Для этого нужно использовать такую конструкцию.
Модификатор implicit говорит компилятору, что преобразование может быть неявным. То есть оно сработает, если написать так:
Explicit, наоборот, означает, что преобразование должно быть явным:
Вот как будет выглядеть перегрузка преобразования объекта класса Hero в int:
Вот как она будет использоваться:
Вывод в консоль будет следующим:
Проблемы читаемости
Несмотря на то, что перегрузки помогают быстро реализовать какой-нибудь функционал, они могут навредить читаемости. Например, не всегда можно сразу понять, зачем в коде складываются два объекта.
Или же непонятно, зачем конвертировать Hero в int. Ясность вносит название переменной (power), но этого недостаточно.
В большинстве случаев лучше использовать более простые решения. Например, можно создать для объекта свойство Power, которое возвращает сумму характеристик.
Вместо сложения объектов можно написать метод Enhance(), который будет принимать другой предмет и прибавлять его характеристики к текущему.
Такие перегрузки стоит использовать либо если вы работаете над кодом один, либо если есть подробная документация.
Домашнее задание
Создайте игру, в которой можно улучшать одни предметы с помощью других. При улучшении предмету добавляется опыт. Когда его станет достаточно, необходимо повысить уровень. Количество опыта должно зависеть от мощи.
Заключение
Полиморфизм — очень удобный инструмент. Однако в этой статье была затронута лишь его часть; чтобы начать работать со второй, нужно ознакомиться с принципами наследования и абстракции.
Вы можете изучить ООП гораздо глубже, записавшись на курс «Профессия C#-разработчик». Он раскрывает лучшие практики работы с C# в объектно-ориентированной парадигме программирования.
Язык программирования C++
Язык С++. Полиморфизм
Полиморфизм – свойство, которое позволяет использовать одно и тоже имя функции для решения двух и более схожих, но технически разных задач. Полиморфизм – возможность замещения методов объекта родителя методами объекта-потомка, имеющих то же имя.
Полиморфизм по-гречески означает «много форм». Объекты, имеющие общего предка, могут принимать разные формы, оставаясь при этом схожими.
Чтобы использовать полиморфизм, необходимо чтобы:
1) все классы-потомки являлись наследниками одного и того же базового класса
2) функция, реализующая метод, должна быть объявлена виртуальной в базовом классе
Виртуальным называется метод, ссылка на который вычисляется на этапе выполнения программы.
Доступ к обычным методам через указатели
Рассмотрим пример, когда базовый и производные классы содержат функции с одни и тем же именем, и к ним обращаются с помощью указателей, но без использования виртуальных функций:
A, B, Base – это типы. Указатели на объекты производных классов совместимы по типу с указателями на объекты базового класса.
Base *ptr; ptr=&a; ptr=&b;
Однако, указатели производных классов между собой не совместимы!
Пример:
A *ptr; ptr=&a;
ptr=&b; // указатель класса A не совместим с указателем класса B.
Теперь необходимо понять, какая собственно функция выполняется в этой строчке
Это функция Base::show() или A::show() или B:show()?
Результат выполнения дает простой ответ
Base
Base
Всегда выполняется метод базового класса. Компилятор не смотрит на содержимое указателя, а выбирает метод, определяемый типом указателя!!
Доступ к виртуальным методам через указатели
Сделаем одно маленькое изменение в нашей программе: поставим ключевое слово virtual перед объявлением функции show() в базовом классе.
Class A
Class B
Теперь выполняются методы производных классов. Один и тот же вызов ставит на выполнение разные функции в зависимости от содержимого указателя ptr.
Если метод в базовом классе объявлен как виртуальный, то компилятор выбирает метод по содержимому указателя, а не по типу указателя, как было в первом примере.
Абстрактные классы и чисто виртуальные методы
Базовый класс, объекты которого никогда не будут реализованы называется абстрактным классом. Такой класс может существовать с единственной целью – быть родительским классом к производным классом, объекты которых будут реализованы.
Для того чтобы сделать базовый класс абстрактным, достаточно ввести в класс хотя бы одну чисто виртуальную функцию.
Чисто виртуальная функция – это функция, после объявления которой добавлено выражение =0.
Пример: Объявить абстрактный класс person. Объявить два производных класса – student и teacher. В каждом из классов объявить метод, с помощью которого можно создать список выдающихся педагогов и студентов. Студентов со средним баллом больше 4 и педагогов с числом публикаций более 50 статей считать выдающимися.