Yah, sepertinya domain semantik Anda memiliki hubungan IS-A, tetapi Anda agak waspada menggunakan subtipe / pewarisan untuk memodelkan ini — terutama karena pantulan tipe runtime. Namun saya pikir Anda takut pada hal yang salah — subtyping memang datang dengan bahaya, tetapi fakta bahwa Anda menanyakan objek saat runtime bukanlah masalahnya. Anda akan melihat apa yang saya maksud.
Pemrograman berorientasi objek telah sangat bergantung pada gagasan hubungan IS-A, itu bisa dibilang terlalu condong padanya, mengarah ke dua konsep kritis terkenal:
Tapi saya pikir ada cara lain yang lebih fungsional berbasis pemrograman untuk melihat hubungan IS-A yang mungkin tidak mengalami kesulitan ini. Pertama, kami ingin memodelkan kuda dan unicorn dalam program kami, jadi kami akan memilikiHorse dan Unicorntipe. Apa nilai dari tipe-tipe ini? Baiklah, saya akan mengatakan ini:
- Nilai dari tipe ini adalah representasi atau deskripsi kuda dan unicorn (masing-masing);
- Mereka terencana representasi atau deskripsi - mereka bukan bentuk bebas, mereka dibangun sesuai dengan aturan yang sangat ketat.
Itu mungkin terdengar jelas, tetapi saya pikir salah satu cara orang masuk ke masalah seperti masalah lingkaran-elips adalah dengan tidak memikirkan poin-poin itu dengan cukup hati-hati. Setiap lingkaran adalah elips, tetapi itu tidak berarti bahwa setiap uraian lingkaran yang terencana secara otomatis merupakan uraian terencana dari elips menurut skema yang berbeda. Dengan kata lain, hanya karena lingkaran adalah elips tidak berarti bahwa a CircleadalahEllipse , jadi untuk berbicara. Tetapi itu berarti:
- Ada fungsi total yang mengkonversi
Circle (deskripsi lingkaran terencana) menjadi Ellipse(berbagai jenis deskripsi) yang menggambarkan lingkaran yang sama;
- Ada fungsi parsial yang mengambil
Ellipsedan, jika menggambarkan lingkaran, mengembalikan yang sesuaiCircle .
Jadi, dalam istilah pemrograman fungsional, Unicorntipe Anda tidak perlu menjadi subtipe Horsesama sekali, Anda hanya perlu operasi seperti ini:
-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse
-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn
Dan toUnicornperlu kebalikan dari toHorse:
toUnicorn (toHorse x) = Just x
MaybeJenis Haskell adalah bahasa lain yang disebut jenis "opsi". Misalnya, tipe Java 8 Optional<Unicorn>adalah an Unicornatau tidak sama sekali. Perhatikan bahwa dua alternatif Anda — melempar pengecualian atau mengembalikan "nilai default atau sihir" - sangat mirip dengan jenis opsi.
Jadi pada dasarnya yang saya lakukan di sini adalah merekonstruksi konsep hubungan IS-A dalam hal jenis dan fungsi, tanpa menggunakan subtipe atau warisan. Apa yang akan saya ambil dari ini adalah:
- Model Anda perlu memiliki
Horse tipe;
- Itu
Horse jenis kebutuhan untuk mengkodekan informasi yang cukup untuk menentukan dengan pasti apakah nilai apapun menggambarkan unicorn;
- Beberapa operasi
Horse tipe perlu memaparkan informasi tersebut sehingga klien tipe dapat mengamati apakah diberikanHorse adalah unicorn;
- Klien
Horsejenis ini harus menggunakan operasi terakhir ini pada saat runtime untuk membedakan antara unicorn dan kuda.
Jadi ini pada dasarnya adalah "tanya setiap Horse apakah itu unicorn". Anda waspada dengan model itu, tapi saya pikir salah. Jika saya memberi Anda daftarHorse s, semua yang dijamin oleh tipenya adalah bahwa hal-hal yang dideskripsikan oleh item dalam daftar adalah kuda — jadi Anda, mau tidak mau, akan perlu melakukan sesuatu saat runtime untuk memberi tahu mereka yang unicorn. Jadi saya kira tidak ada jalan keluarnya - Anda perlu mengimplementasikan operasi yang akan melakukannya untuk Anda.
Dalam pemrograman berorientasi objek, cara yang biasa dilakukan adalah sebagai berikut:
- Punya a
Horse tipe;
- Memiliki
Unicornsebagai subtipe dariHorse ;
- Gunakan refleksi tipe runtime sebagai operasi yang dapat diakses klien yang membedakan apakah yang diberikan
Horseadalah Unicorn.
Ini memang memiliki kelemahan besar, ketika Anda melihatnya dari sudut "hal vs deskripsi" yang saya sajikan di atas:
- Bagaimana jika Anda memiliki sebuah
Horseinstance yang menggambarkan unicorn tetapi bukan sebuah Unicorninstance?
Kembali ke awal, inilah yang saya pikir adalah bagian yang sangat menakutkan tentang menggunakan subtyping dan downcast untuk memodelkan hubungan IS-A ini — bukan fakta bahwa Anda harus melakukan pemeriksaan runtime. Menyalahgunakan tipografi sedikit, menanyakan Horseapakah ini sebuah Unicorninstance tidak sama dengan menanyakan Horseapakah itu unicorn (apakah itu adalah- Horsedeskripsi kuda yang juga unicorn). Tidak, kecuali jika program Anda telah berusaha keras untuk merangkum kode yang membangun Horsessehingga setiap kali klien mencoba untuk membangun Horseyang menggambarkan unicorn, Unicornkelas akan dipakai. Dalam pengalaman saya, jarang programmer melakukan hal ini dengan hati-hati.
Jadi saya akan pergi dengan pendekatan di mana ada operasi eksplisit, non-downcast yang mengubah Horses ke Unicorns. Ini bisa berupa metode Horsetipe:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
... atau itu bisa menjadi objek eksternal ("objek terpisah Anda pada kuda yang memberi tahu Anda apakah kuda itu unicorn atau tidak"):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
Pilihan di antara ini adalah masalah bagaimana program Anda diatur — dalam kedua kasus, Anda memiliki operasi yang setara dengan saya di Horse -> Maybe Unicornatas, Anda hanya mengemasnya dengan cara yang berbeda (yang tentu saja akan memiliki efek riak pada operasi apa yang Horsedibutuhkan tipe tersebut. untuk mengekspos kepada kliennya).