Bagaimana menangani metode yang telah ditambahkan untuk subtipe dalam konteks polimorfisme?

14

Ketika Anda menggunakan konsep polimorfisme Anda membuat hierarki kelas dan menggunakan referensi orang tua Anda memanggil fungsi antarmuka tanpa mengetahui tipe spesifik yang memiliki objek. Ini bagus. Contoh:

Anda memiliki koleksi hewan dan Anda memanggil semua fungsi hewan eatdan Anda tidak peduli apakah itu makan anjing atau kucing. Tetapi dalam hierarki kelas yang sama Anda memiliki hewan yang memiliki tambahan - selain diwariskan dan diimplementasikan dari kelas Animal, misalnya makeEggs, getBackFromTheFreezedStatedan sebagainya. Jadi, dalam beberapa kasus dalam fungsi Anda, Anda mungkin ingin tahu tipe spesifik untuk memanggil perilaku tambahan.

Misalnya, kalau-kalau itu adalah pagi hari dan jika itu hanya binatang maka Anda menelepon eat, jika tidak, itu adalah manusia, maka panggil dulu washHands, getDresseddan baru kemudian panggil eat. Bagaimana cara menangani kasus ini? Polimorfisme mati. Anda perlu mengetahui jenis objek, yang terdengar seperti bau kode. Apakah ada pendekatan umum untuk menangani kasus ini?

Narek
sumber
7
Jenis polimorfisme yang Anda gambarkan disebut subtipe polimorfisme , tetapi bukan satu-satunya jenis (lihat Polimorfisme ). Anda tidak harus membuat hierarki kelas untuk melakukan polimorfisme (dan saya benar-benar berpendapat bahwa pewarisan bukan metode yang paling umum untuk mencapai polimorfisme subtipe, menerapkan antarmuka jauh lebih lazim).
Vincent Savard
24
Jika Anda mendefinisikan Eaterantarmuka dengan eat()metode, maka sebagai klien, Anda tidak peduli bahwa Humanpelaksana harus menelepon terlebih dahulu washHands()dan getDressed(), ini merupakan detail implementasi dari kelas ini. Jika, sebagai klien, Anda peduli akan fakta ini, kemungkinan besar Anda tidak menggunakan alat yang tepat untuk pekerjaan itu.
Vincent Savard
3
Anda juga harus mempertimbangkan bahwa sementara di pagi hari, seorang manusia mungkin perlu getDressedsebelum mereka eat, itu tidak terjadi untuk makan siang. Tergantung pada keadaan Anda, washHands();if !dressed then getDressed();[code to actually eat]mungkin cara terbaik untuk menerapkan ini untuk manusia. Kemungkinan lain adalah bagaimana jika hal-hal lain mengharuskan itu washHandsdan / atau getDresseddipanggil? Misalkan Anda memiliki leaveForWork? Anda mungkin perlu menyusun alur program Anda untuk memilikinya sehingga itu disebut jauh sebelum itu.
Duncan X Simpson
1
Ingatlah bahwa memeriksa terhadap tipe yang tepat mungkin merupakan bau kode dalam OOP tetapi ini adalah praktik yang sangat umum dalam FP (yaitu menggunakan pencocokan pola untuk menentukan jenis serikat yang didiskriminasi dan kemudian menindaklanjutinya).
Theodoros Chatzigiannakis
3
Waspadai contoh ruang sekolah dari hierarki OO seperti binatang. Program nyata hampir tidak pernah memiliki taksonomi yang bersih. Misalnya, ericlippert.com/2015/04/27/wizards-and-warriors-part-one . Atau jika Anda ingin pergi seluruh babi dan mempertanyakan seluruh paradigma: Pemrograman Berorientasi Objek itu Buruk .
jpmc26

Jawaban:

18

Tergantung. Sayangnya tidak ada solusi generik. Pikirkan persyaratan Anda dan coba cari tahu apa yang harus dilakukan hal-hal ini.

Misalnya, Anda mengatakan di pagi hari hewan yang berbeda melakukan hal yang berbeda. Bagaimana Anda memperkenalkan metode getUp()atau prepareForDay()atau sesuatu seperti itu. Kemudian Anda dapat melanjutkan dengan polimorfisme dan membiarkan setiap hewan melakukan rutinitas paginya.

Jika Anda ingin membedakan antara hewan, maka Anda tidak harus menyimpannya tanpa pandang bulu dalam daftar.

Jika tidak ada yang berhasil, maka Anda dapat mencoba Pola Pengunjung , yang merupakan jenis peretasan untuk memungkinkan pengiriman jenis dinamis di mana Anda dapat mengirimkan pengunjung yang akan menerima panggilan balik tipe-tepat dari hewan. Namun saya akan menekankan bahwa ini harus menjadi pilihan terakhir jika semuanya gagal.

Robert Bräutigam
sumber
33

Ini adalah pertanyaan yang bagus dan ini adalah masalah yang banyak orang lakukan ketika mencoba memahami cara menggunakan OO. Saya pikir sebagian besar pengembang berjuang dengan ini. Saya berharap saya bisa mengatakan bahwa sebagian besar bisa melewatinya tetapi saya tidak yakin itu masalahnya. Sebagian besar pengembang, dalam pengalaman saya, akhirnya menggunakan tas properti pseudo-OO .

Pertama, izinkan saya menjelaskan. Ini bukan salahmu. Cara OO biasanya diajarkan sangat cacat. The Animalcontoh adalah pelaku utama, IMO. Pada dasarnya, kita katakan, mari kita bicara tentang objek, apa yang bisa mereka lakukan. Sebuah Animalkaleng eat()dan bisa speak(). Super. Sekarang buat beberapa hewan dan kode cara mereka makan dan berbicara. Sekarang Anda tahu OO, kan?

Masalahnya adalah bahwa ini datang pada OO dari arah yang salah. Mengapa ada hewan dalam program ini dan mengapa mereka perlu berbicara dan makan?

Saya mengalami kesulitan memikirkan penggunaan nyata untuk suatu Animaltipe. Saya yakin itu ada tetapi mari kita bahas sesuatu yang menurut saya lebih mudah untuk dipikirkan: simulasi lalu lintas. Asumsikan kita ingin memodelkan lalu lintas dalam berbagai skenario. Berikut adalah beberapa hal mendasar yang harus kita lakukan untuk dapat melakukannya.

Vehicle
Road
Signal

Kita bisa masuk lebih dalam dengan segala macam hal pejalan kaki dan kereta api, tetapi kita akan membuatnya tetap sederhana.

Mari kita pertimbangkan Vehicle. Kemampuan apa yang dibutuhkan kendaraan? Perlu melakukan perjalanan di jalan. Itu harus bisa berhenti pada sinyal. Itu harus dapat menavigasi persimpangan.

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

Ini mungkin terlalu sederhana tapi ini awal. Sekarang. Bagaimana dengan semua hal lain yang mungkin dilakukan kendaraan? Mereka bisa mematikan jalan dan menjadi parit. Apakah itu bagian dari simulasi? Tidak. Tidak perlu itu. Beberapa mobil dan bus memiliki hidrolika yang memungkinkannya untuk memantul atau berlutut. Apakah itu bagian dari simulasi? Tidak. Tidak perlu itu. Sebagian besar mobil membakar bensin. Beberapa tidak. Apakah pembangkit listrik bagian dari simulasi? Tidak. Tidak perlu itu. Ukuran ban? Tidak butuh itu Navigasi GPS? Sistem infotainment? Tidak butuh mereka.

Anda hanya perlu mendefinisikan perilaku yang akan Anda gunakan. Untuk itu, saya pikir seringkali lebih baik untuk membangun antarmuka OO dari kode yang berinteraksi dengannya. Anda mulai dengan antarmuka kosong dan kemudian mulai menulis kode yang memanggil metode tidak ada. Itulah cara Anda mengetahui metode apa yang Anda butuhkan di antarmuka Anda. Kemudian setelah Anda selesai melakukannya, Anda pergi dan mulai mendefinisikan kelas yang menerapkan perilaku ini. Perilaku yang tidak digunakan tidak relevan dan tidak perlu didefinisikan.

Inti dari OO adalah bahwa Anda dapat menambahkan implementasi baru dari antarmuka ini nanti tanpa mengubah kode panggilan. Satu-satunya cara yang berfungsi adalah jika kebutuhan kode panggilan menentukan apa yang terjadi di antarmuka. Tidak ada cara untuk mendefinisikan semua perilaku dari semua hal yang mungkin yang bisa dipikirkan nanti.

JimmyJames
sumber
13
Ini jawaban yang bagus. "Untuk itu, saya pikir seringkali lebih baik untuk membangun antarmuka OO dari kode yang berinteraksi dengannya." Tentu saja, dan saya berpendapat itu satu-satunya cara. Anda tidak dapat mengetahui kontrak publik dari antarmuka hanya melalui implementasi, itu selalu ditentukan dari perspektif kliennya. (Dan sebagai catatan tambahan, inilah sebenarnya yang dimaksud TDD.)
Vincent Savard
@VincentSavard, "Saya berpendapat itu satu-satunya cara." Kamu benar. Saya kira alasan saya tidak membuatnya absolut adalah bahwa begitu Anda memiliki ide, Anda dapat menyempurnakan antarmuka dan kemudian memperbaikinya dengan cara ini. Pada akhirnya, ketika Anda turun ke paku payung kuningan, itu satu-satunya hal yang penting.
JimmyJames
@ jpmc26 Mungkin sedikit kata yang kuat. Saya tidak yakin saya setuju bahwa jarang menerapkan ini. Saya tidak yakin bagaimana antarmuka dapat berguna jika Anda tidak menggunakannya dengan cara ini selain dari antarmuka marker yang menurut saya adalah ide yang mengerikan.
JimmyJames
9

TL; DR:

Pikirkan abstraksi dan metode yang berlaku untuk semua subclass dan mencakup semua yang Anda butuhkan.

Pertama-tama mari kita tetap dengan eat()teladan Anda .

Merupakan sifat menjadi manusia yang, sebagai prasyarat untuk makan, manusia ingin mencuci tangan dan berpakaian sebelum makan. Jika Anda ingin seseorang datang kepada Anda untuk sarapan bersama Anda, Anda tidak menyuruh mereka mencuci tangan dan berpakaian, mereka melakukannya sendiri ketika Anda mengundang mereka, atau mereka menjawab "Tidak, saya tidak bisa datang selesai, saya belum mencuci tangan, dan saya belum berpakaian ".

Kembali ke perangkat lunak:

Sebagai Humancontoh tidak akan makan tanpa prasyarat, saya akan memiliki Human's eat()metode lakukan washHands()dan getDressed()jika itu belum dilakukan. Seharusnya bukan tugas Anda sebagai eat()penelepon untuk mengetahui kekhasan itu. Alternatif manusia keras kepala adalah dengan melemparkan pengecualian ("Saya tidak siap makan!") Jika prasyarat tidak terpenuhi, membuat Anda frustrasi, tetapi setidaknya memberi tahu bahwa makan tidak berhasil.

Bagaimana dengan makeEggs()?

Saya akan merekomendasikan untuk mengubah cara berpikir Anda. Anda mungkin ingin melaksanakan tugas pagi yang dijadwalkan semua makhluk. Sekali lagi, sebagai penelepon, bukan tugas Anda untuk mengetahui apa tugas mereka. Jadi saya akan merekomendasikan doMorningDuties()metode yang diterapkan semua kelas.

Ralf Kleberhoff
sumber
Saya setuju dengan jawaban ini. Narek benar tentang bau kode. Ini adalah desain antarmuka yang bau, jadi perbaiki itu dan kebaikan Anda.
Jonathan van de Veen
Apa yang dijelaskan jawaban ini biasanya disebut sebagai Prinsip Pergantian Liskov .
Philipp
2

Jawabannya cukup sederhana.

Bagaimana menangani objek yang bisa melakukan lebih dari yang Anda harapkan?

Anda tidak perlu menanganinya karena tidak ada gunanya. Antarmuka biasanya dirancang tergantung pada bagaimana itu akan digunakan. Jika antarmuka Anda tidak mendefinisikan mencuci tangan, maka Anda tidak peduli sebagai penelepon antarmuka; jika Anda melakukannya Anda akan mendesainnya secara berbeda.

Misalnya, kalau-kalau itu adalah pagi hari dan jika itu hanya binatang maka Anda memanggil makan, jika tidak adalah manusia, maka panggilan pertama mencuci tangan, dapatkanDressressed dan hanya kemudian panggilan makan. Bagaimana cara menangani kasus ini?

Misalnya, dalam pseudocode:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

Sekarang Anda menerapkan IMorningPerformeruntuk Animalhanya melakukan makan, dan untuk HumanAnda juga menerapkannya untuk mencuci tangan dan berpakaian. Penelepon metode MorningTime Anda tidak terlalu peduli apakah itu manusia atau binatang. Yang diinginkannya adalah rutinitas pagi yang dilakukan, yang setiap objek lakukan dengan sangat baik berkat OO.

Polimorfisme mati.

Atau apakah itu?

Anda perlu mencari tahu jenis objeknya

Mengapa menganggap itu? Saya pikir ini bisa menjadi asumsi yang salah.

Apakah ada pendekatan umum untuk menangani kasus ini?

Ya, biasanya diselesaikan dengan hierarki kelas atau antarmuka yang dirancang dengan cermat. Perhatikan bahwa dalam contoh di atas tidak ada yang bertentangan dengan contoh Anda seperti yang Anda berikan, namun, Anda mungkin akan merasa tidak puas, karena Anda telah membuat beberapa asumsi lagi bahwa Anda tidak menulis dalam pertanyaan pada saat penulisan , dan asumsi ini mungkin dilanggar.

Dimungkinkan untuk pergi ke lubang kelinci dengan Anda memperketat asumsi Anda dan saya memodifikasi jawaban untuk tetap memuaskan mereka, tetapi saya tidak berpikir itu akan berguna.

Mendesain hierarki kelas yang baik itu sulit dan membutuhkan banyak wawasan tentang domain bisnis Anda. Untuk domain yang kompleks, seseorang harus membahas dua, tiga, atau bahkan lebih iterasi, karena mereka memperbaiki pemahaman mereka tentang bagaimana entitas yang berbeda dalam domain bisnis mereka berinteraksi, sampai mereka tiba pada model yang memadai.

Di situlah contoh hewan sederhana tidak ada. Kami ingin mengajar secara sederhana, tetapi masalah yang kami coba selesaikan tidak jelas sampai Anda masuk lebih dalam, yaitu memiliki pertimbangan dan domain yang lebih kompleks.

Andrew Savinykh
sumber