Существуют различные виды полиморфизма, один из которых обычно представляет собой полиморфизм во время выполнения / динамическая диспетчеризация.
Высокоуровневое описание полиморфизма времени выполнения состоит в том, что вызов метода выполняет разные вещи в зависимости от типа времени выполнения его аргументов: сам объект отвечает за разрешение вызова метода. Это обеспечивает огромную гибкость.
Один из наиболее распространенных способов использования этой гибкости - это внедрение зависимостей , например, чтобы я мог переключаться между различными реализациями или вводить фиктивные объекты для тестирования. Если я заранее знаю, что будет только ограниченное количество возможных вариантов, я мог бы попытаться жестко закодировать их с помощью условных выражений, например:
void foo() {
if (isTesting) {
... // do mock stuff
} else {
... // do normal stuff
}
}
Это делает код сложным для подражания. Альтернатива состоит в том, чтобы ввести интерфейс для этой операции foo и написать нормальную реализацию и фиктивную реализацию этого интерфейса, а также «внедрить» требуемую реализацию во время выполнения. «Внедрение зависимости» - сложный термин для «передачи правильного объекта в качестве аргумента».
В качестве примера из реальной жизни я сейчас работаю над некой проблемой машинного обучения. У меня есть алгоритм, который требует модель прогнозирования. Но я хочу попробовать разные алгоритмы машинного обучения. Итак, я определил интерфейс. Что мне нужно от моей модели прогнозирования? Учитывая некоторую входную выборку, прогноз и его ошибки:
interface Model {
def predict(sample) -> (prediction: float, std: float);
}
Мой алгоритм использует заводскую функцию, которая обучает модель:
def my_algorithm(..., train_model: (observations) -> Model, ...) {
...
Model model = train_model(observations);
...
y, std = model.predict(x)
...
}
Теперь у меня есть различные реализации интерфейса модели, и я могу сравнить их друг с другом. Одна из этих реализаций фактически берет две другие модели и объединяет их в расширенную модель. Так что благодаря этому интерфейсу:
- мой алгоритм не должен знать о конкретных моделях заранее,
- Я могу легко поменять модели, и
- У меня есть большая гибкость в реализации моих моделей.
Классический вариант использования полиморфизма в графических интерфейсах. В среде GUI, такой как Java AWT / Swing /…, есть разные компоненты . Интерфейс компонента / базовый класс описывает такие действия, как рисование себя на экране или реагирование на щелчки мыши. Многие компоненты являются контейнерами, которые управляют подкомпонентами. Как такой контейнер может нарисовать себя?
void paint(Graphics g) {
super.paint(g);
for (Component child : this.subComponents)
child.paint(g);
}
Здесь контейнеру не нужно заранее знать точные типы подкомпонентов - если они соответствуют Component
интерфейсу, контейнер может просто вызывать полиморфный paint()
метод. Это дает мне свободу расширять иерархию классов AWT произвольными новыми компонентами.
В процессе разработки программного обеспечения существует много повторяющихся проблем, которые можно решить, применив полиморфизм в качестве метода. Эти повторяющиеся пары «проблема - решение» называются шаблонами проектирования , и некоторые из них собраны в одноименной книге. В терминах этой книги, моя модель машинного обучения для инъекций будет стратегией, которую я использую, чтобы «определить семейство алгоритмов, инкапсулировать каждый и сделать их взаимозаменяемыми». Пример Java-AWT, где компонент может содержать подкомпоненты, является примером композита .
Но не каждый проект должен использовать полиморфизм (помимо включения внедрения зависимостей для модульного тестирования, что является действительно хорошим вариантом использования). Большинство проблем в остальном очень статичны. Как следствие, классы и методы часто используются не для полиморфизма, а просто как удобные пространства имен и для красивого синтаксиса вызова метода. Например, многие разработчики предпочитают вызовы методов, account.getBalance()
а не эквивалентные вызовы функций Account_getBalance(account)
. Это прекрасный подход, просто многие вызовы «методов» не имеют ничего общего с полиморфизмом.