Как полиморфизм используется в реальном мире? [закрыто]

17

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

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
Кристофер
источник
1
Коллекции, которые вы видите и используете каждый день, сами по себе достаточны, чтобы понять, что такое полиморфизм. Но то, как эффективно использовать полиморфизм в решении проблем, - это навык, который вы приобретаете чаще всего на основе опыта, а не просто обсуждения. Иди и запачкай руки.
Дургадас С
Если у вас есть набор типов, которые все будут поддерживать какой-то минимальный интерфейс (например, набор объектов, которые необходимо нарисовать), интерфейс обычно хорошо подходит для того, чтобы скрыть различия между объектами от вызова для его отрисовки. Также, если вы создаете (или работаете с) API, который имеет методы, которые могут обслуживать базовый объект, и значительное количество типов, которые наследуют его более или менее одинаково , полиморфизм может быть лучшим способом абстрагировать различия между эти типы.
Джу
В общем, если вы часто создаете перегруженные методы для обработки разных типов, а код похож, или если вы if(x is SomeType) DoSomething()часто пишете , возможно, стоит использовать полиморфизм. Для меня полиморфизм - это решение, похожее на то, когда нужно создавать отдельный метод, если я обнаружил, что я повторял код несколько раз, я обычно реорганизую его в метод, и если я обнаружил, что либо делаю if object is this type do thisкод часто, то это может быть Стоит рефакторинг и добавление интерфейса или класса.
Джу

Ответы:

35

Поток является отличным примером полиморфизма.

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

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

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

Euphoric
источник
2
В языках с множественным и виртуальным наследованием, таких как C ++, этот пример может даже продемонстрировать шаблон "страшного ромба" ... путем получения классов входного и выходного потоков из базового класса потока и расширения обоих для создания потока ввода-вывода
круговорот
2
@gyre И все хорошо, нет причин «бояться» ромбовидного узора. Необходимость знать о противоположном аналоге в алмазе и не вызывать конфликты имен с ним - это важно, и проблема, и раздражающая, и причина избегать алмазного узора, где это практически возможно ... но не заходите слишком далеко, опасаясь его простое, скажем, соглашение об именах может решить проблемы.
KRyan
+1 Stream- мой самый любимый пример полиморфизма. Я даже больше не пытаюсь учить людей порочной модели «животное, млекопитающее, собака», Streamно они делают лучше.
Фарап
@KRyan Я не выражал свои собственные мысли, называя его «страшным бриллиантом», я только что слышал, что его называют таковым. Я полностью согласен; Я думаю, что это то, что каждый разработчик должен иметь возможность обернуть голову и использовать соответствующим образом.
gyre
@ Да, я действительно это понял; вот почему я начал с «и», чтобы указать, что это расширение вашей мысли, а не противоречие.
KRyan
7

Типичным примером, связанным с играми, может быть базовый класс Entity, содержащий общих членов, таких как draw()или update().

Для более чистого примера, ориентированного на данные, может быть базовый класс, Serializableобеспечивающий общее saveToStream()и loadFromStream().

Марио
источник
6

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

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

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

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). Это прекрасный подход, просто многие вызовы «методов» не имеют ничего общего с полиморфизмом.

Амон
источник
6

Вы видите много наследования и полиморфизма в большинстве инструментов UI.

Например, в наборе инструментов JavaFX UI Buttonнаследует, от ButtonBaseчего наследует, от Labeledкоторого наследует, от Controlкоторого наследует, от Regionкоторого наследует, от Parentкоторого наследует, от Nodeкоторого наследуется Object. Многие слои перекрывают некоторые методы из предыдущих.

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

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

Тем не менее, наборы инструментов для пользовательского интерфейса также имеют существенный недостаток: они, как правило, огромны . Когда инженер-программист-новичок пытается понять внутреннюю работу общей структуры пользовательского интерфейса, они часто сталкиваются с более чем сотней классов , большинство из которых служат очень эзотерическим целям. «Какого черта это ReadOnlyJavaBeanLongPropertyBuilder? Является ли это важно? У меня есть , чтобы понять , что это хорошо для?» Новички могут легко заблудиться в этой кроличьей норе. Поэтому они могут либо бежать в ужасе, либо остаться на поверхности, где они просто изучают синтаксис и стараются не слишком задумываться о том, что на самом деле происходит под капотом.

Philipp
источник
3

Хотя здесь уже есть хорошие примеры, другой пример - заменить животных устройствами:

  • Deviceможет быть powerOn(), powerOff(), setSleep()а может getSerialNumber().
  • SensorDeviceможет сделать все это, а также обеспечить полиморфные функции , такие как getMeasuredDimension(), getMeasure(), alertAt(threashhold)и autoTest().
  • конечно, getMeasure()не будут реализованы одинаково для датчика температуры, детектора света, детектора звука или объемного датчика. И, конечно же, каждый из этих более специализированных датчиков может иметь некоторые дополнительные функции.
Christophe
источник
2

Презентация является очень распространенным приложением, возможно, наиболее распространенным является ToString (). В основном это Animal.Speak (): вы указываете объекту проявить себя.

Вообще говоря, вы говорите объекту «делать свое дело». Думайте Сохранить, Загрузить, Инициализировать, Удалить, ProcessData, GetStatus.

Мартин Маат
источник
2

Моим первым практическим использованием полиморфизма была реализация Heap в Java.

У меня был базовый класс с реализацией методов insert, removeTop, где разница между max и min Heap была бы только тем, как работает метод сравнения.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Поэтому, когда я хотел иметь MaxHeap и MinHeap, я мог просто использовать наследование.

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}
Базил
источник
1

Вот реальный сценарий полиморфизма таблицы веб-приложения / базы данных :

Я использую Ruby on Rails для разработки веб-приложений, и одна из общих черт многих моих проектов - возможность загружать файлы (фотографии, PDF-файлы и т. Д.). Так, например, у a Userможет быть несколько изображений профиля, а также Productможет быть много изображений продукта. Оба имеют поведение загрузки и хранения изображений, а также изменения размера, создания миниатюр и т. Д. Чтобы оставаться СУХИМЫМИ и делиться поведением Picture, мы хотим сделать Pictureполиморфным, чтобы он мог принадлежать обоим Userи Product.

В Rails я бы проектировал свои модели так:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

и миграция базы данных для создания picturesтаблицы:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

Столбцы imageable_idи imageable_typeиспользуются Rails внутри. По сути, imageable_typeсодержит имя класса ( "User", "Product"и т. Д.) И imageable_idявляется идентификатором связанной записи. Так imageable_type = "User"и imageable_id = 1будет запись в usersтаблице с id = 1.

Это позволяет нам делать user.picturesдоступ к фотографиям пользователя, а также product.picturesполучать изображения продукта. Затем все поведение, связанное с изображениями, инкапсулируется в Photoклассе (а не в отдельном классе для каждой модели, для которой требуются фотографии), поэтому все остается СУХИМЫМ.

More reading: Рельсы полиморфные ассоциации .

Крис Сирефице
источник
0

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

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

То, что я описал выше, является примером полиморфизма во время выполнения, тогда как перегрузка метода является примером полиморфизма времени компиляции, где complier в зависимости от типов параметров i / p и o / p и количества параметров связывает вызывающую программу с правильным методом во время самого завершения.

Надеюсь, это проясняет.

rahulaga_dev
источник