Як використовується поліморфізм у реальному світі? [зачинено]


17

Я намагаюся зрозуміти, як поліморфізм використовується в реальному проекті життя, але я можу знайти лише класичний приклад (або щось подібне до нього) мати Animalбатьківський клас із методом speak(), і багато дитячих класів, які перекривають цей метод, і тепер ви можете викликати метод speak()на будь-якому з дочірніх об'єктів, наприклад:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
Колекцій, які ви бачите та використовуєте щодня, саме по собі достатньо, щоб зрозуміти, що таке поліморфізм. Але як ефективно використовувати поліморфізм у вирішенні проблем - це навичка, яку ви набуваєте найчастіше на досвіді, а не просто на обговоренні. Продовжуйте і забруднити руки.
Durgadass S

Якщо у вас є набір типів, які всі підтримуватимуть якийсь мінімальний інтерфейс (наприклад, набір об'єктів, який потрібно намалювати), інтерфейс, як правило, добре підходить для приховування відмінностей між об'єктами від виклику, щоб намалювати його. Крім того, якщо ви створюєте (або працюєте з) API, який має методи, які можуть обслуговувати базовий об'єкт і значну кількість типів, які успадковують його більш-менш однаково , поліморфізм може бути найкращим способом абстрагування відмінностей між ці типи.
jrh

Загалом, якщо ви часто використовуєте перевантажені методи для обробки різних типів і код схожий, або якщо ви пишете if(x is SomeType) DoSomething()часто, можливо, варто скористатися поліморфізмом. Для мене поліморфізм - це рішення, подібне до того, коли зробити окремий метод, якщо я виявив, що повторював код кілька разів, зазвичай переробляю його на метод, і якщо я виявляю, що або я if object is this type do thisчасто роблю код, це може бути варто рефакторинг та додавання інтерфейсу чи класу.
jrh

Відповіді:


35

Потік - чудовий приклад поліморфізму.

Потік представляє "послідовність байтів, які можна прочитати або записати". Але ця послідовність може виходити з файлів, пам'яті або багатьох видів мережевих з'єднань. Або він може служити декоратором, який обертає існуючий потік і певним чином перетворює байти, як шифрування чи стиснення.

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

Деякі кажуть, що Streamце неправильний приклад поліморфізму, оскільки він визначає багато "особливостей", які його виконавці не підтримують, як мережевий потік, що дозволяє лише читати чи писати, але не обидва одночасно. Або відсутність пошуку. Але це лише питання складності, оскільки Streamйого можна розділити на багато частин, які можна було б реалізувати самостійно.


2
У мовах з множинним і віртуальним успадкуванням, як C ++, цей приклад навіть може продемонструвати шаблон "жахливого алмазу" ... шляхом виведення класів потоку вводу та виведення з базового класу потоку та розширення обох для створення потоку вводу / виводу
gyre

2
@gyre І добре, що немає ніяких причин "боятися" алмазного малюнка. Необхідність усвідомлювати протилежний аналог в алмазі і не викликати конфлікт імен з ним важливо, і виклик, і дратівливий, і аргумент, щоб уникнути алмазного малюнка там, де це практично можливо, але не надто перелякаючи його, коли просто, скажімо, конвенція про іменування може вирішити проблеми.
KRyan

+1 Streams - мій улюблений приклад поліморфізму за весь час. Я навіть не намагаюся більше навчити людей хибної моделі «тварини, ссавці, собаки», Streamале робити кращу роботу.
Фарап

@KRyan Я не висловлював власні думки, називаючи це "жахливим діамантом", я щойно чув, як його називають таким. Я повністю згоден; Я думаю, що це те, що кожен розробник повинен мати можливість обернути голову та використовувати належним чином.
gyre

@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)
  ...
}

Зараз у мене є різні реалізації модельного інтерфейсу і можу порівняти їх одна проти одної. Одна з цих реалізацій насправді включає дві інші моделі та об'єднує їх у розширену модель. Тож завдяки цьому інтерфейсу:

  • моєму алгоритму не потрібно заздалегідь знати про конкретні моделі,
  • Я можу легко замінити моделі, і
  • Я маю велику гнучкість у реалізації своїх моделей.

Класичний випадок використання поліморфізму є в графічних інтерфейсах. У такій графічній основі, як 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

У більшості наборів інструментів користувальницького інтерфейсу ви бачите багато успадкованості та поліморфізму.

Наприклад, у інструментарії користувальницького інтерфейсу JavaFX Buttonуспадковується від ButtonBaseякого успадковується, від Labeledякого успадковується, від Controlякого успадковується, від Regionякого успадковується, від Parentякого успадковується, від Nodeякого успадковується Object. Багато шарів перекривають деякі методи від попередніх.

Коли ви хочете, щоб ця кнопка з’явилася на екрані, ви додаєте її до коду Pane, який може прийняти все, що успадковується, Nodeяк дитина. Але як Pane знає, що робити з кнопкою, коли вона просто сприймає це як загальний об'єкт Node? Цей об’єкт міг бути чим завгодно. Панель може зробити це тому, що кнопка переосмислює методи Node будь-якою логікою, що залежить від кнопки. Панель просто викликає методи, визначені в Node, а решту залишає самому об'єкту. Це ідеальний приклад застосованого поліморфізму.

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

Однак у наборів інструментів інтерфейсу також є вагомий недолік: вони, як правило, величезні . Коли інженер-програмний інженер-неофіт намагається зрозуміти внутрішню роботу загальної системи інтерфейсу, вони часто стикаються із сотнями класів , більшість з яких служать дуже езотеричним цілям. "Що за чорт ReadOnlyJavaBeanLongPropertyBuilder? Це важливо? Чи потрібно розуміти, для чого це добре?" Початківці можуть легко загубитися в цій кролячій норі. Тож вони можуть або втекти від терору, або залишитися на поверхні, де вони просто вивчать синтаксис і намагаються не думати надто сильно про те, що насправді відбувається під кришкою.


3

Хоча тут вже є приємні приклади, ще один - замінити тварин пристроями:

  • Deviceможе бути powerOn(), powerOff(), setSleep()а може getSerialNumber().
  • SensorDeviceможе зробити все це, а також забезпечити поліморфні функції , такі як getMeasuredDimension(), getMeasure(), alertAt(threashhold)і autoTest().
  • звичайно, getMeasure()не будуть реалізовані однаково для датчика температури, світлодіодного детектора, звукового сповіщувача або об'ємного датчика. І звичайно, кожен із цих більш спеціалізованих датчиків може мати деякі додаткові функції.

2

Презентація - це дуже поширений додаток, можливо, найпоширеніший - ToString (). Що в основному є Animal.Speak (): Ви кажете об'єкту проявити себе.

Більш загально кажучи, ви говорите об'єкту "робити свою справу". Подумайте Зберегти, Завантажити, Ініціалізувати, Утилізувати, ProcessData, GetStatus.


2

Моє перше практичне використання поліморфізму - це реалізація Heap in 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-файли тощо). Так, наприклад, 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класі (а не окремим класом для кожної моделі, яка потребує фотографій), тому речі зберігаються ДУХОМО.

Більше читання: Рейки поліморфних асоціацій .


0

Існує багато алгоритмів сортування, таких як сортування бульбашок, сортування вставки, швидке сортування, сортування купи тощо, і вони мають різну складність, і який з них оптимально використовувати, залежить від різних факторів (наприклад: розмір масиву)

Клієнт, що надає інтерфейс сортування, стосується лише надання масиву як вхідного даних, а потім отримання відсортованого масиву. Під час виконання, залежно від певних факторів, може бути використана відповідна реалізація сортування. Це один із реальних прикладів використання поліморфізму.

Те, що я описав вище, є прикладом поліморфізму виконання, тоді як перевантаження методу - це приклад поліморфності часу компіляції, коли в залежності від типів параметрів i / p та o / p та кількості параметрів зв'язує абонента з правильним методом у сам час часу.

Сподіваюсь, це прояснює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.