Tahun terakhir ini saya mengambil lompatan dan belajar bahasa pemrograman fungsional (F #) dan salah satu hal yang lebih menarik yang saya temukan adalah bagaimana hal itu mempengaruhi cara saya mendesain perangkat lunak OO. Dua hal yang paling saya lewatkan dalam bahasa OO adalah pencocokan pola dan jumlah. Di mana-mana saya melihat saya melihat situasi-situasi yang secara sepele akan dimodelkan dengan serikat yang didiskriminasi, tetapi saya enggan menggunakan linggis dalam beberapa implementasi OO DU yang terasa tidak wajar bagi paradigma tersebut.
Ini biasanya membuat saya membuat tipe perantara untuk menangani or
hubungan yang akan digunakan tipe sum untuk saya. Tampaknya juga mengarah ke banyak percabangan. Jika saya membaca orang-orang seperti Misko Hevery , ia menyarankan bahwa desain OO yang baik dapat meminimalkan percabangan melalui polimorfisme.
Salah satu hal yang saya hindari sebisa mungkin dalam kode OO adalah tipe dengan null
nilai. Jelas or
hubungan dapat dimodelkan dengan tipe dengan satu null
nilai dan satu non- null
nilai, tetapi ini berarti null
menguji di mana-mana. Apakah ada cara untuk memodelkan tipe-tipe yang heterogen tetapi terkait secara logis secara polimorf? Strategi atau pola desain akan sangat membantu, atau sekadar cara untuk berpikir tentang tipe yang heterogen dan terkait secara umum dalam paradigma OO.
sumber
Jawaban:
Seperti halnya Anda, saya berharap serikat yang didiskriminasi lebih merata; Namun, alasan mereka berguna dalam sebagian besar bahasa fungsional adalah karena mereka menyediakan pencocokan pola lengkap, dan tanpa ini, mereka hanya sintaks yang cukup: tidak hanya pencocokan pola: pencocokan pola lengkap , sehingga kode tidak dapat dikompilasi jika Anda tidak t tutupi setiap kemungkinan: inilah yang memberi Anda kekuatan.
Satu-satunya cara untuk melakukan sesuatu yang berguna dengan tipe penjumlahan adalah dengan menguraikannya, dan bercabang bergantung pada jenisnya (misalnya dengan pencocokan pola). Hal hebat tentang antarmuka adalah bahwa Anda tidak peduli apa jenis sesuatu itu, karena Anda tahu Anda bisa memperlakukannya seperti
iface
: tidak ada logika unik yang diperlukan untuk setiap jenis: tidak ada percabangan.Ini bukan "kode fungsional memiliki lebih banyak percabangan, kode OO memiliki lebih sedikit", ini adalah "'bahasa fungsional' lebih cocok untuk domain di mana Anda memiliki serikat - yang mandat bercabang - dan 'bahasa OO' lebih cocok untuk kode di mana Anda dapat mengekspos perilaku umum sebagai antarmuka umum - yang mungkin terasa kurang bercabang ". Percabangan adalah fungsi dari desain dan domain Anda. Sederhananya, jika "tipe heterogen tetapi terkait secara logis" Anda tidak dapat mengekspos antarmuka umum, maka Anda harus mencocokkan / mencocokkan pola di atasnya. Ini adalah masalah domain / desain.
Apa yang Misko maksudkan adalah gagasan umum bahwa jika Anda dapat mengekspos tipe Anda sebagai antarmuka umum, maka menggunakan fitur OO (interface / polimorfisme) akan membuat hidup Anda lebih baik dengan menempatkan perilaku tipe-spesifik dalam tipe daripada dalam mengkonsumsi kode.
Penting untuk mengenali bahwa antarmuka dan serikat pekerja adalah kebalikan dari satu sama lain: antarmuka mendefinisikan beberapa hal yang harus diimplementasikan oleh tipe , dan serikat mendefinisikan beberapa hal yang harus dipertimbangkan konsumen . Jika Anda menambahkan metode ke antarmuka, Anda telah mengubah kontrak itu, dan sekarang setiap jenis yang sebelumnya diterapkan harus diperbarui. Jika Anda menambahkan jenis baru ke serikat pekerja, Anda telah mengubah kontrak itu, dan sekarang setiap pola lengkap yang cocok dengan serikat pekerja harus diperbarui. Mereka mengisi peran yang berbeda, dan meskipun kadang-kadang mungkin untuk menerapkan sistem 'cara baik', yang Anda gunakan adalah keputusan desain: tidak ada yang secara inheren lebih baik.
Salah satu keuntungan menggunakan antarmuka / polimorfisme adalah bahwa kode pengkonsumsi lebih dapat diperluas: Anda dapat mengirimkan tipe yang tidak ditentukan pada waktu desain, asalkan mengekspos antarmuka yang disepakati. Di sisi lain, dengan penyatuan statis, Anda dapat mengeksploitasi perilaku yang tidak dipertimbangkan pada waktu desain dengan menulis kecocokan pola yang lengkap, asalkan tetap pada kontrak serikat.
Mengenai 'Pola Obyek Null': ini bukan peluru perak, dan tidak menggantikan
null
cek. Semua itu memberikan cara untuk menghindari beberapa pemeriksaan 'null' di mana perilaku 'null' dapat diekspos di belakang antarmuka umum. Jika Anda tidak dapat mengekspos perilaku 'nol' di belakang antarmuka tipe itu, maka Anda akan berpikir "Saya benar-benar berharap saya bisa dengan lengkap mencocokkan pola ini" dan akhirnya akan melakukan pemeriksaan 'percabangan'.sumber
Ada cara yang cukup "standar" untuk menyandikan tipe penjumlahan ke bahasa berorientasi objek.
Inilah dua contoh:
Dalam C #, kita dapat menguraikan ini sebagai:
F # lagi:
C # lagi:
Pengkodean sepenuhnya mekanis. Pengkodean ini menghasilkan hasil yang memiliki sebagian besar kelebihan dan kekurangan yang sama dari tipe data aljabar. Anda juga dapat mengenali ini sebagai variasi dari Pola Pengunjung. Kami bisa mengumpulkan parameter untuk
Match
bersama-sama menjadi antarmuka yang bisa kami sebut Pengunjung.Di sisi keuntungan, ini memberi Anda pengkodean jumlah jenis berprinsip. (Ini adalah pengkodean Scott .) Ini memberi Anda "pencocokan pola" yang lengkap meskipun hanya satu "lapisan" yang cocok pada suatu waktu.
Match
dalam beberapa hal merupakan antarmuka "lengkap" untuk tipe-tipe ini dan operasi tambahan apa pun yang kita inginkan dapat didefinisikan dalam hal itu. Ini menyajikan perspektif yang berbeda pada banyak pola OO seperti Pola Objek Null dan Pola Negara seperti yang saya tunjukkan pada jawaban Ryathal, serta Pola Pengunjung dan Pola Komposit. TheOption
/Maybe
tipe seperti Null Object Pola generik. Pola Komposit mirip dengan pengkodeantype Tree<'a> = Leaf of 'a | Children of List<Tree<'a>>
. Pola Negara pada dasarnya adalah penyandian enumerasi.Di sisi kerugiannya, ketika saya menulisnya,
Match
metode ini menempatkan beberapa batasan pada subclass apa yang dapat ditambahkan secara bermakna, terutama jika kita ingin mempertahankan Properti Pengganti Liskov. Misalnya, menerapkan pengodean ini ke tipe enumerasi tidak akan memungkinkan Anda untuk memperpanjang enumerasi. Jika Anda memang ingin memperpanjang enumerasi, Anda harus mengubah semua penelepon dan pelaksana di mana-mana sama seperti jika Anda menggunakanenum
danswitch
. Yang mengatakan, pengkodean ini agak lebih fleksibel daripada yang asli. Sebagai contoh, kita dapat menambahkanAppend
implementorList
yang hanya menampung dua daftar yang memberi kita waktu tambahan. Ini akan berperilaku seperti daftar ditambahkan bersama tetapi akan diwakili dengan cara yang berbeda.Tentu saja, banyak dari masalah ini berkaitan dengan fakta yang
Match
agak (secara konseptual tetapi sengaja) terkait dengan subkelas. Jika kami menggunakan metode yang tidak terlalu spesifik, kami mendapatkan desain OO yang lebih tradisional dan kami mendapatkan kembali ekstensibilitas, tetapi kami kehilangan "kelengkapan" antarmuka dan karenanya kami kehilangan kemampuan untuk mendefinisikan operasi apa pun pada jenis ini dalam hal antarmuka. Seperti yang disebutkan di tempat lain, ini adalah manifestasi dari Masalah Ekspresi .Dapat diperdebatkan, desain seperti di atas dapat digunakan secara sistematis untuk sepenuhnya menghilangkan kebutuhan percabangan yang pernah mencapai ideal OO. Smalltalk, misalnya, menggunakan pola ini sering termasuk untuk Boolean sendiri. Tetapi seperti diskusi sebelumnya menyarankan, "penghapusan percabangan" ini cukup ilusi. Kami baru saja mengimplementasikan percabangan dengan cara yang berbeda, dan masih memiliki banyak properti yang sama.
sumber
Menangani null dapat dilakukan dengan pola objek nol . Idenya adalah untuk membuat instance objek Anda yang mengembalikan nilai default untuk setiap anggota dan memiliki metode yang tidak melakukan apa-apa, tetapi juga jangan salah. Ini tidak menghilangkan cek nol sepenuhnya, tetapi itu berarti Anda hanya perlu memeriksa nol pada pembuatan objek dan mengembalikan objek nol Anda.
The pola negara adalah cara untuk meminimalkan bercabang dan memberikan beberapa manfaat dari pencocokan pola. Sekali lagi itu mendorong logika percabangan ke pembuatan objek. Setiap negara adalah implementasi terpisah dari antarmuka dasar, jadi semua kode pengkonsumsi hanya perlu memanggil DoStuff () dan metode yang tepat dipanggil. Beberapa bahasa juga menambahkan pencocokan pola sebagai fitur, C # adalah salah satu contohnya.
sumber