Diberi kawanan kuda, bagaimana cara menemukan panjang tanduk rata-rata semua unicorn?

30

Pertanyaan di atas adalah contoh abstrak dari masalah umum yang saya temui dalam kode lama, atau lebih tepatnya, masalah yang dihasilkan dari upaya sebelumnya dalam memecahkan masalah ini.

Saya bisa memikirkan setidaknya satu .NET framework metode yang dimaksudkan untuk mengatasi masalah ini, seperti Enumerable.OfType<T>metode ini. Tetapi fakta bahwa Anda akhirnya menginterogasi jenis objek saat runtime tidak cocok dengan saya.

Selain bertanya pada setiap kuda, "Apakah Anda unicorn?" pendekatan-pendekatan berikut juga muncul dalam pikiran:

  • Melempar pengecualian ketika upaya dilakukan untuk mendapatkan panjang tanduk non-unicorn (memperlihatkan fungsi yang tidak sesuai untuk setiap kuda)
  • Mengembalikan nilai default atau magic untuk panjang tanduk non-unicorn (memerlukan cek default yang dibumbui di seluruh kode yang ingin mengelompokkan statistik tanduk pada sekelompok kuda yang semuanya bisa non-unicorn)
  • Hapus warisan dan buatlah objek terpisah pada kuda yang memberi tahu Anda apakah kuda itu unicorn atau tidak (yang berpotensi mendorong masalah yang sama ke lapisan bawah)

Saya merasa ini akan menjadi jawaban terbaik dengan "tidak dijawab." Tetapi bagaimana Anda mendekati masalah ini dan jika itu tergantung, bagaimana konteks keputusan Anda?

Saya juga tertarik dengan wawasan tentang apakah masalah ini masih ada dalam kode fungsional (atau mungkin hanya ada dalam bahasa fungsional yang mendukung kemampuan berubah-ubah?)

Ini ditandai sebagai kemungkinan duplikat dari pertanyaan berikut: Bagaimana cara menghindari downcasting?

Jawaban atas pertanyaan itu mengasumsikan bahwa seseorang memiliki a HornMeasurerdengan mana semua pengukuran tanduk harus dilakukan. Tapi itu cukup memaksakan basis kode yang dibentuk dengan prinsip egaliter bahwa setiap orang harus bebas untuk mengukur tanduk kuda.

Jika tidak ada HornMeasurer, pendekatan jawaban yang diterima mencerminkan pendekatan berbasis pengecualian yang tercantum di atas.

Ada juga beberapa kebingungan dalam komentar tentang apakah kuda dan unicorn keduanya kuda, atau jika unicorn adalah subspesies kuda ajaib. Kedua kemungkinan harus dipertimbangkan - mungkin yang satu lebih disukai dari yang lain?

moarboilerplate
sumber
22
Kuda tidak memiliki tanduk, jadi rata-rata tidak terdefinisi (0/0).
Scott Whitlock
3
@moarboilerplate Di mana saja dari 10 hingga tak terbatas.
pengasuh
4
@StephenP: Itu tidak akan berhasil secara matematis untuk kasus ini; semua 0s akan condong rata-rata.
Mason Wheeler
3
Jika pertanyaan Anda paling baik dijawab dengan non-jawaban, maka pertanyaan itu bukan milik situs tanya jawab; reddit, quora, atau situs berbasis diskusi lainnya dibangun untuk jenis barang yang tidak dijawab ... yang mengatakan, saya pikir itu mungkin bisa dijawab dengan jelas jika Anda mencari kode yang diberikan @MasonWheeler, jika tidak saya pikir saya tidak tahu apa yang Anda coba tanyakan ..
Jimmy Hoffa
3
@JimmyHoffa "Anda salah" kebetulan merupakan "non-jawaban" yang dapat diterima, dan seringkali lebih baik daripada "baik, inilah satu cara Anda bisa melakukannya" - tidak perlu diskusi panjang.
moarboilerplate

Jawaban:

11

Dengan asumsi Anda ingin memperlakukan Unicornsebagai jenis khusus Horse, pada dasarnya ada dua cara Anda dapat memodelkannya. Cara yang lebih tradisional adalah hubungan subclass. Anda dapat menghindari memeriksa jenis dan downcasting dengan hanya refactoring kode Anda untuk selalu menjaga daftar terpisah dalam konteks di mana hal itu penting, dan hanya menggabungkannya dalam konteks di mana Anda tidak pernah peduli tentang Unicornsifat - sifat. Dengan kata lain, Anda mengaturnya sehingga Anda tidak pernah masuk ke situasi di mana Anda perlu mengekstrak unicorn dari kawanan kuda di tempat pertama. Ini tampaknya sulit pada awalnya, tetapi mungkin dalam 99,99% kasus, dan biasanya benar-benar membuat kode Anda jauh lebih bersih.

Cara lain Anda bisa membuat model unicorn hanya dengan memberikan semua kuda panjang tanduk opsional. Kemudian Anda dapat menguji apakah itu unicorn dengan memeriksa apakah ia memiliki panjang tanduk, dan menemukan panjang tanduk rata-rata semua unicorn dengan (dalam Scala):

case class Horse(val hornLength: Option[Double])

val horse = Horse(None)
val unicorn = Horse(Some(12.0))
val anotherUnicorn = Horse(Some(6.0))

val herd = List(horse, unicorn, anotherUnicorn)
val hornLengths = herd flatMap {_.hornLength}
val averageLength = hornLengths.sum / hornLengths.size

Metode ini memiliki keuntungan menjadi lebih lugas, dengan satu kelas, tetapi kerugian karena jauh lebih tidak bisa diperluas, dan memiliki semacam cara memutar untuk memeriksa "unicornness." Kiatnya jika Anda menggunakan solusi ini adalah mengenali kapan Anda mulai sering memperluasnya sehingga Anda perlu pindah ke arsitektur yang lebih fleksibel. Solusi semacam ini jauh lebih populer dalam bahasa fungsional di mana Anda memiliki fungsi yang sederhana dan kuat seperti flatMapdengan mudah menyaring Noneitem.

Karl Bielefeldt
sumber
7
Tentu saja, ini mengasumsikan bahwa satu-satunya perbedaan antara kuda biasa dan unicorn adalah tanduk. Jika ini bukan masalahnya, maka segala sesuatunya menjadi jauh lebih rumit dengan sangat cepat.
Mason Wheeler
@MasonWheeler hanya pada metode kedua yang disajikan.
moarboilerplate
1
Cari komentar tentang bagaimana non-unicorn dan unicorn tidak boleh dituliskan bersama dalam skenario warisan sampai Anda berada dalam konteks di mana Anda tidak peduli dengan unicorn. Tentu, .OfType () dapat menyelesaikan masalah dan menyelesaikan masalah, tapi ini memecahkan masalah yang seharusnya tidak ada. Adapun pendekatan kedua, ini berhasil karena opsi jauh lebih unggul daripada mengandalkan nol untuk menyiratkan sesuatu. Saya pikir pendekatan kedua dapat dicapai dalam OO dengan kompromi jika Anda merangkum sifat unicorn di properti mandiri dan sangat waspada.
moarboilerplate
1
kompromi jika Anda merangkum sifat unicorn di properti mandiri dan sangat waspada - mengapa membuat hidup sulit untuk diri sendiri. Gunakan typeof secara langsung dan menghemat banyak masalah di masa depan.
gbjbaanb
@ gbjbaanb Saya akan mempertimbangkan pendekatan yang hanya benar-benar sesuai untuk skenario di mana anemia Horsememiliki IsUnicornproperti dan beberapa jenis UnicornStuffproperti dengan panjang tanduk di atasnya (ketika scaling untuk pengendara / glitter yang disebutkan dalam pertanyaan Anda).
moarboilerplate
38

Anda sudah cukup banyak membahas semua opsi. Jika Anda memiliki perilaku yang bergantung pada subtipe tertentu, dan dicampur dengan tipe lain, kode Anda harus mengetahui subtipe itu; itu alasan logis sederhana.

Secara pribadi, saya hanya pergi dengan horses.OfType<Unicorn>().Average(u => u.HornLength). Ini mengungkapkan maksud dari kode dengan sangat jelas, yang sering kali merupakan hal yang paling penting karena seseorang akhirnya harus memeliharanya nanti.

Mason Wheeler
sumber
Maafkan saya jika sintaks lambda saya tidak benar; Saya tidak terlalu suka C # coder dan saya tidak pernah bisa menyimpan detail misterius seperti ini secara langsung. Namun harus jelas apa yang saya maksud.
Mason Wheeler
1
Jangan khawatir, masalahnya cukup banyak dipecahkan begitu daftar hanya berisi Unicorns saja (untuk catatan Anda bisa menghilangkan return).
moarboilerplate
4
Ini adalah jawaban yang saya akan pilih jika saya ingin menyelesaikan masalah dengan cepat. Tapi bukan jawabannya jika saya ingin refactor kode menjadi lebih masuk akal.
Andy
6
Ini jelas merupakan jawaban kecuali Anda memerlukan beberapa tingkat optimasi yang absurd. Kejelasan dan keterbacaan itu membuat hampir semua hal lain diperdebatkan.
David mengatakan Reinstate Monica
1
@ David Grinberg bagaimana jika menulis metode yang bersih dan mudah dibaca ini berarti bahwa Anda harus terlebih dahulu menerapkan struktur warisan yang sebelumnya tidak ada?
moarboilerplate
9

Tidak ada yang salah dengan .NET dengan:

var unicorn = animal as Unicorn;
if(unicorn != null)
{
    sum += unicorn.HornLength;
    count++;
}

Menggunakan persamaan Linq juga baik-baik saja:

var averageUnicornHornLength = animals
    .OfType<Unicorn>()
    .Select(x => x.HornLength)
    .Average();

Berdasarkan pertanyaan yang Anda tanyakan dalam judul, ini adalah kode yang saya harapkan untuk ditemukan. Jika pertanyaannya diajukan seperti "apa rata-rata hewan dengan tanduk" itu akan berbeda:

var averageHornedAnimalHornLength = animals
    .OfType<IHornedAnimal>()
    .Select(x => x.HornLength)
    .Average();

Perhatikan bahwa ketika menggunakan Linq, Average(dan Mindan Max) akan mengeluarkan pengecualian jika enumerable kosong dan tipe T tidak dapat dibatalkan. Itu karena rata-rata benar-benar tidak terdefinisi (0/0). Jadi Anda benar-benar membutuhkan sesuatu seperti ini:

var hornedAnimals = animals
    .OfType<IHornedAnimal>()
    .ToList();
if(hornedAnimals.Count > 0)
{
    var averageHornLengthOfHornedAnimals = hornedAnimals
        .Average(x => x.HornLength);
}
else
{
    // deal with it in your own way...
}

Edit

Saya hanya berpikir ini perlu menambahkan ... salah satu alasan pertanyaan seperti ini tidak cocok dengan pemrogram berorientasi objek adalah mengasumsikan kita menggunakan kelas dan objek untuk memodelkan struktur data. Ide berorientasi objek Smalltalk-esque asli adalah untuk menyusun program Anda keluar dari modul yang dipakai sebagai objek dan melakukan layanan untuk Anda ketika Anda mengirim mereka pesan. Fakta bahwa kita juga dapat menggunakan kelas dan objek untuk memodelkan struktur data adalah efek samping (berguna), tetapi mereka adalah dua hal yang berbeda. Saya bahkan tidak berpikir yang terakhir harus dianggap pemrograman berorientasi objek, karena Anda bisa melakukan hal yang sama dengan struct, tapi itu tidak akan secantik itu.

Jika Anda menggunakan pemrograman berorientasi objek untuk membuat layanan yang melakukan hal-hal untuk Anda, maka menanyakan apakah layanan itu sebenarnya beberapa layanan lain atau implementasi konkret umumnya disukai karena alasan yang baik. Anda diberi antarmuka (biasanya melalui injeksi dependensi) dan Anda harus kode ke antarmuka / kontrak itu.

Di sisi lain, jika Anda (salah-) menggunakan ide-ide kelas / objek / antarmuka untuk membuat struktur data atau model data, maka saya pribadi tidak melihat masalah dengan menggunakan ide is-a secara maksimal. Jika Anda telah menetapkan bahwa unicorn adalah sub-jenis kuda dan itu benar-benar masuk akal dalam domain Anda, maka silakan saja dan minta kuda-kuda dalam kawanan Anda untuk menemukan unicorn. Lagi pula, dalam kasus seperti ini kami biasanya mencoba membuat bahasa khusus domain untuk mengekspresikan solusi dari masalah yang harus kami selesaikan dengan lebih baik. Dalam hal itu tidak ada yang salah dengan .OfType<Unicorn>()dll.

Pada akhirnya, mengambil koleksi item dan memfilternya pada tipe sebenarnya hanya pemrograman fungsional, bukan pemrograman berorientasi objek. Untungnya bahasa seperti C # nyaman menangani kedua paradigma sekarang.

Scott Whitlock
sumber
7
Anda sudah tahu itu animal adalah Unicorn ; hanya membuang daripada menggunakan as, atau berpotensi menggunakan lebih baik as dan kemudian memeriksa nol.
Philip Kendall
3

Tetapi fakta bahwa Anda akhirnya menginterogasi jenis objek saat runtime tidak cocok dengan saya.

Masalah dengan pernyataan ini adalah, tidak peduli mekanisme apa yang Anda gunakan, Anda akan selalu menginterogasi objek untuk mengetahui jenisnya. Itu bisa RTTI atau bisa menjadi gabungan atau struktur data biasa di mana Anda bertanya if horn > 0. Spesifik persis berubah sedikit tetapi tujuannya sama - Anda bertanya objek tentang dirinya sendiri dalam beberapa cara untuk melihat apakah Anda harus menginterogasinya lebih lanjut.

Karena itu, masuk akal untuk menggunakan dukungan bahasa Anda untuk melakukan ini. Di .NET Anda akan menggunakan typeofmisalnya.

Alasan untuk melakukan ini melampaui hanya menggunakan bahasa Anda dengan baik. Jika Anda memiliki objek yang terlihat seperti yang lain tetapi untuk beberapa perubahan kecil, kemungkinan Anda akan menemukan lebih banyak perbedaan dari waktu ke waktu. Dalam contoh unicorn / kuda Anda, Anda mungkin mengatakan hanya panjang tanduk ... tapi besok Anda akan memeriksa untuk melihat apakah pengendara potensial adalah perawan, atau jika kotorannya berkilauan. (contoh dunia nyata klasik adalah widget GUI yang berasal dari basis umum dan Anda harus mencari kotak centang dan kotak daftar secara berbeda. Jumlah perbedaan akan terlalu besar untuk hanya membuat objek super tunggal yang menampung semua kemungkinan permutasi data ).

Jika memeriksa jenis objek saat runtime tidak berlaku, maka alternatif Anda adalah dengan membagi objek yang berbeda sejak awal - alih-alih menyimpan kawanan unicorn / kuda, Anda memegang 2 koleksi - satu untuk kuda, satu untuk unicorn . Ini dapat bekerja dengan sangat baik, bahkan jika Anda menyimpannya dalam wadah khusus (misalnya multimap di mana kuncinya adalah jenis objek ... tapi kemudian meskipun kami menyimpannya dalam 2 kelompok, kami segera kembali menginterogasi jenis objek !)

Tentunya pendekatan berbasis pengecualian salah. Menggunakan pengecualian sebagai aliran program normal adalah bau kode (jika Anda memiliki kawanan unicorn dan keledai dengan kerang yang ditempelkan di kepalanya, maka saya akan mengatakan pendekatan berbasis pengecualian tidak masalah, tetapi jika Anda memiliki kawanan unicorn dan kuda kemudian memeriksa masing-masing unicorn-ness tidak terduga. Pengecualian adalah untuk keadaan luar biasa, bukan ifpernyataan yang rumit ). Bagaimanapun, menggunakan pengecualian untuk masalah ini hanya menginterogasi tipe objek saat runtime, hanya di sini Anda menyalahgunakan fitur bahasa untuk memeriksa objek non-unicorn. Anda mungkin juga kode dalamif horn > 0 dan setidaknya memproses koleksi Anda dengan cepat, jelas, menggunakan lebih sedikit baris kode dan menghindari masalah yang muncul sejak pengecualian lain dilemparkan (mis. koleksi kosong, atau mencoba mengukur kerang keledai itu)

gbjbaanb
sumber
Dalam konteks warisan, if horn > 0cukup banyak cara masalah ini diselesaikan pada awalnya. Maka masalah yang biasanya muncul adalah ketika Anda ingin memeriksa pengendara dan kilau, dan horn > 0dikubur di semua tempat dalam kode yang tidak terkait (juga kode menderita bug misteri karena kurangnya pemeriksaan ketika tanduk 0). Juga, mengelompokkan kuda berdasarkan fakta biasanya merupakan proposisi paling mahal, jadi saya biasanya tidak cenderung melakukannya jika mereka masih menulis bersama di akhir refactor. Jadi itu tentu saja menjadi "betapa jeleknya alternatifnya"
moarboilerplate
@moarboilerplate Anda katakan sendiri, pergi dengan solusi yang murah dan mudah dan itu akan berubah menjadi berantakan. Itu sebabnya bahasa OO diciptakan, sebagai solusi untuk masalah semacam ini. kuda subklas mungkin tampak mahal pada awalnya, tetapi segera membayar untuk dirinya sendiri. Melanjutkan solusi yang sederhana, namun berlumpur, semakin mahal seiring berjalannya waktu.
gbjbaanb
3

Karena pertanyaan memiliki functional-programmingtag, kita bisa menggunakan tipe penjumlahan untuk mencerminkan dua rasa kuda dan pencocokan pola untuk membedakan keduanya. Misalnya, dalam F #:

type Equine =
| Horse
| Unicorn of hornLength: float

module equines =

  let averageHornLength (equines : Equine list) =
    equines 
    |> List.choose (fun x -> 
      match x with
      | Unicorn u -> Some(u)
      | _ -> None)
    |> List.average

let herd = [ Horse ; Horse ; Unicorn(35.0) ; Horse ; Unicorn(50.0) ]

printfn "Average horn length in herd : %f" (equines.averageHornLength herd) // prints 42.5

Lebih dari OOP, FP memiliki keunggulan pemisahan data / fungsi, yang mungkin menyelamatkan Anda dari "nurani bersalah" (yang tidak dibenarkan?) Karena melanggar tingkat abstraksi saat downcasting ke subtipe tertentu dari daftar objek dari supertype.

Berbeda dengan solusi OO yang diusulkan dalam jawaban lain, pencocokan pola juga memberikan titik perpanjangan yang lebih mudah jika spesies Horned lain Equinemuncul suatu hari.

guillaume31
sumber
2

Bentuk pendek dari jawaban yang sama di akhir membutuhkan membaca buku atau artikel web.

Pola Pengunjung

Masalahnya memiliki campuran kuda dan unicorn. (Melanggar prinsip substitusi Liskov adalah masalah umum dalam basis kode warisan.)

Tambahkan metode ke kuda dan semua subclass

Horse.visit(EquineVisitor v)

Antar muka pengunjung kuda terlihat seperti ini di java / c #

interface EquineVisitor {
  void visitHorse(Horse z);
  void visitUnicorn(Unicorn z);
}

Unicorn.visit(EquineVisitor v){
   v.visitUnicorn(this);
}

Horse.visit(EquineVisitor v){
   v.visitHorse(this);
}

Untuk mengukur tanduk kita sekarang menulis ....

class HornMeasurer implements EquineVistor {
    void visitHorse(Horse h){} // ignore horses
    void visitUnicorn(Unicorn u){
         double len = u.getHornLength();
         totalLength+=len;
         unicornCount++;
    }

    double getAverageLength(){
          return totalLength/unicornCount;
    }

    double totalLength=0;
    int unicornCount=0;
}

Pola pengunjung dikritik karena membuat refactoring dan pertumbuhan lebih sulit.

Jawaban Singkat: Gunakan pola desain Pengunjung untuk mendapatkan pengiriman ganda.

lihat juga https://en.wikipedia.org/wiki/Visitor_pattern

lihat juga http://c2.com/cgi/wiki?VisitorPattern untuk diskusi tentang pengunjung.

lihat juga Pola Desain oleh Gamma et al.

Tim Williscroft
sumber
Saya akan menjawab dengan pola pengunjung sendiri. Harus gulir ke bawah cara yang mengejutkan untuk menemukan jika seseorang telah menyebutkannya!
Ben Thurley
0

Dengan asumsi bahwa dalam arsitektur Anda unicorn adalah subspesies kuda dan Anda menemukan tempat di mana Anda mendapatkan koleksi di Horsemana beberapa dari mereka mungkin Unicorn, saya pribadi akan pergi dengan metode pertama ( .OfType<Unicorn>()...) karena itu adalah cara paling mudah untuk mengekspresikan niat Anda . Bagi siapa saja yang datang kemudian (termasuk diri Anda dalam 3 bulan), segera jelas apa yang ingin Anda capai dengan kode itu: pilih unicorn dari antara kuda-kuda.

Metode lain yang Anda daftarkan terasa seperti cara lain untuk mengajukan pertanyaan "Apakah Anda unicorn?". Misalnya, jika Anda menggunakan semacam metode berbasis pengecualian untuk mengukur tanduk, Anda mungkin memiliki kode yang akan terlihat seperti ini:

foreach (var horse in horses)
{
    try
    {
        var length = horse.MeasureHorn();
        //...
    }
    catch (NoHornException e)
    {
        continue;
    }
}

Jadi sekarang pengecualian menjadi indikator bahwa sesuatu bukan unicorn. Dan sekarang ini bukan situasi yang luar biasa lagi, tetapi merupakan bagian dari aliran program normal. Dan menggunakan pengecualian bukannya iftampak lebih kotor daripada hanya melakukan pemeriksaan tipe.

Katakanlah Anda pergi rute nilai ajaib untuk memeriksa tanduk pada kuda. Jadi sekarang kelas Anda terlihat seperti ini:

class Horse
{
    public double MeasureHorn() { return -1; }
    //...
}

class Unicorn : Horse
{
    public override double MeasureHorn { return _hornLength; }
    //...
}

Sekarang Horsekelas Anda harus tahu tentang Unicornkelas dan memiliki metode tambahan untuk menangani hal-hal yang tidak dipedulikannya. Sekarang bayangkan Anda juga memiliki Pegasusdan Zebras yang mewarisi dari Horse. Sekarang Horsemembutuhkan Flymetode serta MeasureWings, CountStripes, dll Dan kemudian Unicornkelas mendapat metode ini juga. Sekarang semua kelas Anda harus tahu tentang satu sama lain dan Anda telah mencemari kelas dengan banyak metode yang seharusnya tidak ada di sana hanya untuk menghindari bertanya pada sistem tipe "Apakah ini unicorn?"

Jadi bagaimana dengan menambahkan sesuatu untuk Horsedikatakan jika ada sesuatu Unicorndan menangani semua pengukuran tanduk? Nah, sekarang Anda harus memeriksa keberadaan objek ini untuk mengetahui apakah ada sesuatu yang unicorn (yang hanya menggantikan satu cek dengan yang lain). Ini juga mengeruhkan air sedikit di bahwa sekarang Anda mungkin memilikiList<Horse> unicornsyang benar-benar menampung semua unicorn, tetapi sistem tipe dan debugger tidak dapat dengan mudah mengatakan itu. "Tapi aku tahu itu semua unicorn," katamu, "nama itu bahkan berkata begitu." Nah, bagaimana jika ada sesuatu yang tidak disebutkan namanya? Atau katakan, Anda menulis sesuatu dengan asumsi bahwa itu benar-benar semua unicorn, tetapi kemudian persyaratan berubah dan sekarang mungkin juga ada pegasi yang tercampur? (Karena hal seperti ini tidak pernah terjadi, terutama dalam perangkat lunak warisan / sarkasme.) Sekarang sistem jenis akan dengan senang hati menempatkan pegasi Anda dengan unicorn Anda. Jika variabel Anda telah dinyatakan sebagai List<Unicorn>kompiler (atau lingkungan runtime) akan cocok jika Anda mencoba untuk mencampur pegasi atau kuda.

Akhirnya, semua metode ini hanyalah pengganti untuk pemeriksaan jenis sistem. Secara pribadi, saya lebih suka tidak menemukan kembali roda di sini dan berharap bahwa kode saya berfungsi sama baiknya dengan sesuatu yang ada di dalamnya dan telah diuji oleh ribuan coders lainnya ribuan kali.

Pada akhirnya, kode harus dapat dimengerti oleh Anda . Komputer akan mengetahuinya terlepas dari bagaimana Anda menulisnya. Anda adalah orang yang harus men-debug dan dapat mempertimbangkannya. Tentukan pilihan yang membuat pekerjaan Anda lebih mudah. Jika karena alasan tertentu, salah satu metode lain ini menawarkan keuntungan bagi Anda yang lebih besar daripada kode yang lebih jelas di beberapa tempat yang akan ditampilkan, lakukan saja. Tetapi itu tergantung pada basis kode Anda.

Becuzz
sumber
Pengecualian yang diam itu pasti buruk - proposal saya adalah pemeriksaan yang akan if(horse.IsUnicorn) horse.MeasureHorn();dan pengecualian tidak akan tertangkap - mereka akan dipicu kapan !horse.IsUnicorndan Anda berada dalam konteks pengukuran unicorn, atau di dalam MeasureHornpada non-unicorn. Dengan begitu ketika pengecualian dilemparkan Anda tidak menutupi kesalahan, itu benar-benar meledak dan merupakan pertanda sesuatu perlu diperbaiki. Jelas itu hanya sesuai untuk skenario tertentu, tetapi ini adalah implementasi yang tidak menggunakan melempar pengecualian untuk menentukan jalur eksekusi.
moarboilerplate
0

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:

  1. Nilai dari tipe ini adalah representasi atau deskripsi kuda dan unicorn (masing-masing);
  2. 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:

  1. Ada fungsi total yang mengkonversiCircle (deskripsi lingkaran terencana) menjadi Ellipse(berbagai jenis deskripsi) yang menggambarkan lingkaran yang sama;
  2. 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:

  1. Model Anda perlu memiliki Horse tipe;
  2. Itu Horse jenis kebutuhan untuk mengkodekan informasi yang cukup untuk menentukan dengan pasti apakah nilai apapun menggambarkan unicorn;
  3. Beberapa operasi Horse tipe perlu memaparkan informasi tersebut sehingga klien tipe dapat mengamati apakah diberikanHorse adalah unicorn;
  4. 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).

sakundim
sumber
-1

Komentar OP dalam jawaban lain menjelaskan pertanyaan itu, pikir saya

itulah bagian dari pertanyaan yang ditanyakan juga. Jika saya memiliki kawanan kuda, dan beberapa di antaranya secara unicorn secara konseptual, bagaimana seharusnya mereka ada sehingga masalah dapat diselesaikan dengan bersih tanpa terlalu banyak dampak negatif?

Frasa seperti itu, saya pikir kita perlu lebih banyak informasi. Jawabannya mungkin tergantung pada beberapa hal:

  • Fasilitas bahasa kami. Misalnya, saya mungkin akan mendekati ini secara berbeda dalam ruby ​​dan javascript dan Java.
  • Konsep itu sendiri: Apa itu kuda dan apa itu unicorn? Data apa yang dikaitkan dengan masing-masing? Apakah mereka persis sama kecuali untuk klakson, atau apakah mereka memiliki perbedaan lain?
  • Bagaimana lagi kita menggunakannya, selain mengambil rata-rata panjang tanduk? Dan bagaimana dengan ternak? Mungkin kita harus memodelkannya juga? Apakah kita menggunakannya di tempat lain? herd.averageHornLength()tampaknya cocok dengan model konseptual kami.
  • Bagaimana kuda dan benda unicorn dibuat? Apakah mengubah kode itu dalam batas-batas refactoring kami?

Namun secara umum, saya bahkan tidak akan berpikir tentang warisan dan subtipe di sini. Anda memiliki daftar objek. Beberapa objek tersebut dapat diidentifikasi sebagai unicorn, mungkin karena mereka memiliki ahornLength() metode. Saring daftar berdasarkan properti unik-unicorn ini. Sekarang masalahnya telah dikurangi menjadi rata-rata panjang tanduk dari daftar unicorn.

OP, beri tahu saya jika saya masih salah paham ...

Jonah
sumber
1
Poin yang adil. Untuk mencegah masalah menjadi lebih abstrak kita harus membuat beberapa asumsi yang masuk akal: 1) bahasa yang sangat diketik 2) kawanan membatasi kuda ke satu jenis, kemungkinan karena koleksi 3) teknik seperti mengetik bebek mungkin harus dihindari . Adapun apa yang bisa diubah, tidak harus selalu ada batasan, tetapi setiap jenis perubahan memiliki konsekuensi uniknya sendiri ...
moarboilerplate
Jika kawanan membatasi kuda ke satu jenis, bukan hanya warisan pilihan kita (tidak suka opsi itu) atau objek pembungkus (misalnya HerdMember) yang kita inisialisasi dengan kuda atau unicorn (membebaskan kuda dan unicorn dari memerlukan hubungan subtipe) ). HerdMemberkemudian bebas untuk mengimplementasikan isUnicorn()namun itu sesuai, dan solusi penyaringan saran saya berikut.
Jonah
Dalam beberapa bahasa, hornLength () dapat dicampur, dan jika itu masalahnya, itu bisa menjadi solusi yang valid. Namun, dalam bahasa-bahasa di mana pengetikan kurang fleksibel, Anda harus menggunakan beberapa teknik peretasan untuk melakukan hal yang sama, atau Anda harus melakukan sesuatu seperti meletakkan panjang tanduk pada kuda di mana itu dapat menyebabkan kebingungan dalam kode karena kuda tidak ' t secara konseptual memiliki tanduk. Juga, jika melakukan perhitungan matematis, termasuk nilai-nilai standar dapat memiringkan hasilnya (lihat komentar di bawah pertanyaan asli)
moarboilerplate
Mixins, meskipun, kecuali mereka selesai runtime, hanyalah warisan dengan nama lain. Komentar Anda "seekor kuda secara konseptual tidak memiliki tanduk" berhubungan dengan komentar saya tentang perlunya mengetahui lebih banyak tentang mereka, jika jawaban kita perlu mencakup bagaimana kita memodelkan kuda dan unicorn dan apa hubungan mereka satu sama lain. Solusi apa pun yang mencakup nilai-nilai default salah tangan.
Jonah
Anda benar dalam hal itu untuk mendapatkan solusi yang tepat untuk manifestasi spesifik dari masalah ini Anda harus memiliki banyak konteks. Untuk menjawab pertanyaan Anda tentang seekor kuda dengan tanduk dan untuk mengikatnya kembali ke mixin, saya memikirkan sebuah skenario di mana panjang tanduk bercampur dengan seekor kuda yang bukan unicorn adalah sebuah kesalahan. Pertimbangkan A Scala trait yang memiliki implementasi default untuk hornLength yang melempar pengecualian. Tipe unicorn dapat mengesampingkan implementasi itu, dan jika seekor kuda membuatnya menjadi konteks di mana hornLength dievaluasi, itu merupakan pengecualian.
moarboilerplate
-2

Metode GetUnicorns () yang mengembalikan IEnumerable tampaknya solusi yang paling elegan, fleksibel dan universal bagi saya. Dengan cara ini Anda bisa berurusan dengan (kombinasi dari) sifat-sifat yang menentukan apakah seekor kuda akan lulus sebagai unicorn, bukan hanya tipe kelas atau nilai properti tertentu.

Martin Maat
sumber
Saya setuju dengan ini. Mason Wheeler memiliki solusi yang baik dalam jawabannya juga, tetapi jika Anda perlu memilih unicorn karena berbagai alasan di tempat yang berbeda, kode Anda akan memiliki banyak horses.ofType<Unicorn>...konstruksi. Memiliki GetUnicornsfungsi akan menjadi one-liner, tetapi akan lebih tahan terhadap perubahan pada hubungan kuda / unicorn dari perspektif pemanggil.
Shaz
@Ryan Jika Anda mengembalikan sebuah IEnumerable<Horse>, meskipun kriteria unicorn Anda ada di satu tempat, itu dirangkum, jadi penelepon Anda harus membuat asumsi tentang mengapa mereka memerlukan unicorn (saya bisa mendapatkan clam chowder dengan memesan sup hari ini, tapi itu tidak ' t berarti saya akan mendapatkannya besok dengan melakukan hal yang sama). Plus, Anda harus mengekspos nilai default untuk klakson pada Horse. Jika Unicornjenisnya sendiri, Anda harus membuat tipe baru dan mempertahankan pemetaan tipe, yang dapat menyebabkan overhead.
moarboilerplate
1
@moarboilerplate: Kami menganggap semua itu mendukung solusi. Bagian kecantikannya adalah ia tidak tergantung pada detail implementasi unicorn. Apakah Anda membedakan berdasarkan anggota data, kelas atau waktu (kuda-kuda itu semua bisa berubah menjadi unicorn di tengah malam jika bulan tepat untuk semua yang saya tahu), solusinya tetap, antarmuka tetap sama.
Martin Maat