Jak wykorzystuje się polimorfizm w prawdziwym świecie? [Zamknięte]

17

Próbuję zrozumieć, w jaki sposób polimorfizm jest wykorzystywany w prawdziwym projekcie, ale mogę znaleźć tylko klasyczny przykład (lub coś podobnego do niego) posiadania Animalklasy nadrzędnej z metodą speak()oraz wielu klas podrzędnych, które zastępują tę metodę, a teraz możesz wywołać metodę speak()na dowolnym obiekcie potomnym, na przykład:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();
Krzysztof
źródło
1
Kolekcje, które widzisz i których używasz każdego dnia, same w sobie wystarczają, aby zrozumieć, czym jest polimorfizm. Ale jak efektywnie wykorzystywać polimorfizm w rozwiązywaniu problemów to umiejętność, którą zdobywasz przede wszystkim poprzez doświadczenie, a nie tylko dyskusję. Idź i zabrudz ręce.
Durgadass S
Jeśli masz zestaw typów, które wszystkie obsługują pewnego rodzaju minimalny interfejs (np. Zestaw obiektów, które muszą zostać narysowane), interfejs jest zwykle dobrym rozwiązaniem, aby ukryć różnice między obiektami przed wywołaniem, aby je narysować. Również jeśli tworzysz (lub pracujesz) interfejs API, który ma metody, które mogą obsługiwać obiekt podstawowy i znaczną liczbę typów, które dziedziczą z niego mniej więcej w ten sam sposób , polimorfizm może być najlepszym sposobem na wyodrębnienie różnic między te typy.
jrh
Ogólnie, jeśli często tworzysz przeciążone metody obsługi różnych typów, a kod jest podobny, lub jeśli piszesz if(x is SomeType) DoSomething()często, warto zastosować polimorfizm. Dla mnie polimorfizm jest decyzją podobną do tego, kiedy stworzyć osobną metodę, jeśli stwierdzę, że kilkakrotnie powtórzyłem kod, zwykle refaktoryzuję go do metody, a jeśli stwierdzę, że albo if object is this type do thisczęsto tworzę kod, może być warte refaktoryzacji i dodania interfejsu lub klasy.
jrh

Odpowiedzi:

35

Strumień jest doskonałym przykładem polimorfizmu.

Strumień reprezentuje „sekwencję bajtów, które można odczytać lub zapisać”. Ale ta sekwencja może pochodzić z pliku, pamięci lub wielu rodzajów połączeń sieciowych. Lub może służyć jako dekorator, który otacza istniejący strumień i przekształca bajty w jakiś sposób, na przykład szyfrowanie lub kompresję.

W ten sposób klient korzystający ze Stream nie musi dbać o to, skąd pochodzą bajty. Tylko, że można je czytać po kolei.

Niektórzy twierdzą, że Streamjest to zły przykład polimorfizmu, ponieważ definiuje on wiele „funkcji”, których jego implementatory nie obsługują, na przykład strumień sieciowy umożliwiający tylko odczyt lub zapis, ale nie oba jednocześnie. Lub brak poszukiwania. Ale to tylko kwestia złożoności, ponieważ Streammożna ją podzielić na wiele części, które można zaimplementować niezależnie.

Euforyk
źródło
2
W językach z wielokrotnym i wirtualnym dziedziczeniem, takich jak C ++, ten przykład może nawet zademonstrować wzorzec „przerażającego diamentu” ... poprzez wyprowadzenie klas strumienia wejściowego i wyjściowego z klasy strumienia bazowego i rozszerzenie obu na utworzenie strumienia we / wy
gyre
2
@gyre I zrobione dobrze, nie ma powodu, aby „bać się” wzoru diamentu. Ważne jest, aby być świadomym przeciwnego odpowiednika diamentu i nie powodować konfliktów nazw z nim, jest to wyzwanie, i denerwujące, i powód, aby unikać wzoru diamentu tam, gdzie jest to możliwe ... ale nie obawiaj się zbytnio, gdy po prostu powiedzmy, że konwencja nazewnictwa może rozwiązać problemy.
KRyan,
+1 Streamsą moim ulubionym przykładem polimorfizmu. Nawet nie próbuję już uczyć ludzi wadliwego modelu „zwierzę, ssak, pies”, Streamale wykonuję lepszą pracę.
Pharap
@ KR Ryan Nie wyrażałem własnych myśli, nazywając go „przerażającym diamentem”, właśnie to słyszałem. Całkowicie się zgadzam; Myślę, że jest to coś, co każdy programista powinien umieć owinąć głową i odpowiednio wykorzystać.
gyre
@gyre Oh, tak, właściwie to mam; dlatego zacząłem od „i”, aby wskazać, że jest to przedłużenie twojej myśli, a nie sprzeczność.
KRyan
7

Typowym przykładem związanym z grami byłaby klasa podstawowa Entity, zapewniająca zwykłych członków, takich jak draw()lub update().

Dla bardziej czystego zorientowanego na dane przykładu mogłaby istnieć klasa podstawowa Serializablezapewniająca wspólne saveToStream()i loadFromStream().

Mario
źródło
6

Istnieją różne rodzaje polimorfizmów, tym, który jest przedmiotem zainteresowania, jest zazwyczaj polimorfizm / dynamiczna wysyłka w czasie wykonywania.

Bardzo wysokim poziomem opisu polimorfizmu środowiska wykonawczego jest to, że wywołanie metody robi różne rzeczy w zależności od typu argumentów środowiska wykonawczego: sam obiekt jest odpowiedzialny za rozstrzygnięcie wywołania metody. Pozwala to na dużą elastyczność.

Jednym z najczęstszych sposobów korzystania z tej elastyczności jest wstrzykiwanie zależności , np. Dzięki czemu mogę przełączać się między różnymi implementacjami lub wstrzykiwać fałszywe obiekty do testowania. Jeśli wiem z góry, że będzie ograniczona liczba możliwych wyborów, mógłbym spróbować zakodować je na stałe za pomocą warunkowych, np .:

void foo() {
  if (isTesting) {
    ... // do mock stuff
  } else {
    ... // do normal stuff
  }
}

To sprawia, że ​​kod jest trudny do naśladowania. Alternatywą jest wprowadzenie interfejsu dla tej operacji foo i napisanie normalnej implementacji i pozornej implementacji tego interfejsu oraz „wstrzyknięcie” pożądanej implementacji w czasie wykonywania. „Wstrzyknięcie zależności” jest skomplikowanym terminem określającym „przekazanie poprawnego obiektu jako argumentu”.

Jako przykład ze świata rzeczywistego pracuję obecnie nad pewnym problemem związanym z uczeniem maszynowym. Mam algorytm, który wymaga modelu predykcyjnego. Ale chcę wypróbować różne algorytmy uczenia maszynowego. Więc zdefiniowałem interfejs. Czego potrzebuję od mojego modelu predykcyjnego? Biorąc pod uwagę próbkę wejściową, prognozę i jej błędy:

interface Model {
  def predict(sample) -> (prediction: float, std: float);
}

Mój algorytm przyjmuje funkcję fabryczną, która trenuje model:

def my_algorithm(..., train_model: (observations) -> Model, ...) {
  ...
  Model model = train_model(observations);
  ...
  y, std = model.predict(x)
  ...
}

Mam teraz różne implementacje interfejsu modelu i mogę porównać je ze sobą. Jedna z tych implementacji faktycznie bierze dwa inne modele i łączy je w model wzmocniony. Dzięki temu interfejsowi:

  • mój algorytm nie musi wcześniej wiedzieć o konkretnych modelach,
  • Mogę łatwo wymieniać modele i
  • Mam dużą elastyczność we wdrażaniu moich modeli.

Klasycznym przykładem zastosowania polimorfizmu są GUI. W środowisku GUI, takim jak Java AWT / Swing /…, istnieją różne komponenty . Interfejs komponentu / klasa bazowa opisuje działania, takie jak malowanie się na ekranie lub reagowanie na kliknięcia myszą. Wiele komponentów to kontenery, które zarządzają podkomponentami. Jak taki pojemnik może się rysować?

void paint(Graphics g) {
  super.paint(g);
  for (Component child : this.subComponents)
    child.paint(g);
}

W tym przypadku kontener nie musi z góry wiedzieć o dokładnych typach podskładników - o ile są one zgodne z Componentinterfejsem, kontener może po prostu wywołać paint()metodę polimorficzną . Daje mi to swobodę rozszerzania hierarchii klas AWT o dowolne nowe komponenty.

Istnieje wiele powtarzających się problemów podczas tworzenia oprogramowania, które można rozwiązać, stosując polimorfizm jako technikę. Te powtarzające się pary problem-rozwiązanie nazywane są wzorcami projektowymi , a niektóre z nich są zebrane w księdze o tej samej nazwie. Zgodnie z tą książką, mój model uczenia maszynowego z wtryskiem byłby strategią , której używam do „zdefiniowania rodziny algorytmów, kapsułkowania każdego z nich i uczynienia ich wymiennymi”. Przykład Java-AWT, w którym komponent może zawierać podskładniki, jest przykładem kompozytu .

Ale nie każdy projekt musi wykorzystywać polimorfizm (poza umożliwieniem wstrzykiwania zależności do testów jednostkowych, co jest naprawdę dobrym przypadkiem użycia). Większość problemów jest poza tym bardzo statyczna. W rezultacie klasy i metody często nie są używane do polimorfizmu, ale po prostu jako wygodne przestrzenie nazw i ładna składnia wywołań metod. Np. Wielu programistów woli wywołania metod, account.getBalance()niż w większości równoważne wywołania funkcji Account_getBalance(account). To bardzo dobre podejście, tyle, że wiele wywołań „metod” nie ma nic wspólnego z polimorfizmem.

amon
źródło
6

W większości zestawów narzędzi interfejsu użytkownika widać wiele elementów dziedziczenia i polimorfizmu.

Na przykład w zestawie narzędzi interfejsu użytkownika JavaFX Buttondziedziczy, z ButtonBasektórego dziedziczy, z Labeledktórego dziedziczy, z Controlktórego dziedziczy, z Regionktórego dziedziczy, z Parentktórego dziedziczy, z Nodektórego dziedziczy Object. Wiele warstw zastępuje niektóre metody z poprzednich.

Jeśli chcesz, aby ten przycisk pojawił się na ekranie, dodaj go do Pane, który może zaakceptować wszystko, co odziedziczy po Nodenim. Ale skąd okienko wie, co zrobić z przyciskiem, gdy po prostu widzi go jako ogólny obiekt węzła? Tym obiektem może być wszystko. Panel może to zrobić, ponieważ przycisk na nowo definiuje metody węzła za pomocą dowolnej logiki specyficznej dla przycisku. Panel po prostu wywołuje metody zdefiniowane w węźle i pozostawia resztę samemu obiektowi. To doskonały przykład zastosowanego polimorfizmu.

Zestawy narzędzi interfejsu użytkownika mają bardzo duże znaczenie w świecie rzeczywistym, dzięki czemu są przydatne do nauczania zarówno ze względów naukowych, jak i praktycznych.

Jednak zestawy narzędzi interfejsu użytkownika mają również znaczną wadę: są one zwykle ogromne . Kiedy inżynier oprogramowania neofita próbuje zrozumieć wewnętrzne funkcjonowanie wspólnego frameworku interfejsu użytkownika, często napotyka ponad sto klas , z których większość służy bardzo ezoterycznym celom. „Co do cholery jest ReadOnlyJavaBeanLongPropertyBuilder? Czy to ważne? Czy muszę mieć , aby zrozumieć, co to jest dobre dla?” Początkujący mogą łatwo zgubić się w tej nory królika. Mogą więc albo uciekać w przerażeniu, albo pozostać na powierzchni, gdzie tylko uczą się składni i starają się nie myśleć zbyt intensywnie o tym, co naprawdę dzieje się pod maską.

Philipp
źródło
3

Chociaż istnieją już ładne przykłady, innym jest zastąpienie zwierząt urządzeniami:

  • Devicemoże być powerOn(), powerOff(), setSleep()i puszki getSerialNumber().
  • SensorDeviceMożna zrobić to wszystko i zapewniają polimorficzne funkcje, takie jak getMeasuredDimension(), getMeasure(), alertAt(threashhold)i autoTest().
  • oczywiście getMeasure()nie będą realizowane w ten sam sposób dla czujnika temperatury, detektora światła, detektora dźwięku lub czujnika wolumetrycznego. I oczywiście każdy z tych bardziej wyspecjalizowanych czujników może mieć dostępne dodatkowe funkcje.
Christophe
źródło
2

Prezentacja jest bardzo popularną aplikacją, być może najczęstszą jest ToString (). Który jest w zasadzie Animal.Speak (): Mówisz obiektowi, aby się zamanifestował.

Mówiąc bardziej ogólnie, mówisz przedmiotowi, aby „zrobił to, co on”. Pomyśl Zapisz, Załaduj, Zainicjuj, Usuń, ProcessData, GetStatus.

Martin Maat
źródło
2

Moim pierwszym praktycznym zastosowaniem polimorfizmu była implementacja Heap w java.

Miałem klasę podstawową z implementacją metod insert, removeTop, gdzie różnica między maksymalnym i minimalnym stosem byłaby tylko sposobem porównania metod.

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

Więc kiedy chciałem mieć MaxHeap i MinHeap, mogłem po prostu użyć dziedziczenia.

class MaxHeap extends Heap {

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

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}
Bazil
źródło
1

Oto scenariusz z życia polimorfizmu aplikacji sieci Web / bazy danych :

Używam Ruby on Rails do tworzenia aplikacji internetowych, a jedną z cech wspólnych wielu moich projektów jest możliwość przesyłania plików (zdjęć, plików PDF itp.). Na przykład, Usermoże mieć wiele zdjęć profilowych, a także Productmoże mieć wiele zdjęć produktów. Oba mają działanie przesyłania i przechowywania obrazów, a także zmiany rozmiaru, generowania miniaturek itp. Aby pozostać SUCHYM i dzielić się tym zachowaniem Picture, chcemy uczynić Picturepolimorficzny, aby mógł należeć zarówno do, jak Useri do Product.

W Railsach zaprojektowałbym moje modele jako takie:

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

oraz migracja bazy danych, aby utworzyć picturestabelę:

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

Kolumny imageable_idi imageable_typesą używane wewnętrznie przez Railsy. Zasadniczo, imageable_typeposiada nazwę klasy ( "User", "Product"etc.), i imageable_idjest id powiązanego rekordu. Tak imageable_type = "User"i imageable_id = 1będzie rekord w userstabeli z id = 1.

To pozwala nam robić rzeczy, takie jak user.picturesuzyskiwanie dostępu do zdjęć użytkownika, a także product.picturesuzyskiwanie zdjęć produktu. Następnie wszystkie zachowania związane z obrazem są zawarte w Photoklasie (a nie w osobnej klasie dla każdego modelu, który potrzebuje zdjęć), więc rzeczy pozostają SUCHE.

Więcej informacji: skojarzenia polimorficzne w szynach .

Chris Cirefice
źródło
0

Dostępnych jest wiele algorytmów sortowania, takich jak sortowanie bąbelkowe, sortowanie za pomocą wstawiania, sortowanie szybkie, sortowanie sterty itp. Mają one różną złożoność, a wybór jednego z nich jest optymalny i zależy od różnych czynników (np. Rozmiaru tablicy)

Klient wyposażony w interfejs sortowania dotyczy tylko dostarczenia tablicy jako danych wejściowych, a następnie otrzymania posortowanej tablicy. Podczas działania w zależności od pewnych czynników można zastosować odpowiednią implementację sortowania. Jest to jeden z prawdziwych przykładów użycia polimorfizmu.

To, co opisałem powyżej, jest przykładem polimorfizmu środowiska uruchomieniowego, podczas gdy przeciążenie metody jest przykładem polimorfizmu w czasie kompilacji, w którym jest on zgodny w zależności od typów parametrów i / p i o / p oraz liczby parametrów wiążących osobę wywołującą odpowiednią metodę w samym czasie zgodności.

Mam nadzieję, że to wyjaśnia.

rahulaga_dev
źródło