Ini sangat menarik bagi saya yang keunggulannya memberikan pendekatan "global root class" untuk kerangka kerja. Dengan kata-kata sederhana alasan apa yang dihasilkan .NET framework dirancang untuk memiliki satu kelas objek root dengan fungsi umum yang cocok untuk semua kelas.
Saat ini kami sedang merancang kerangka kerja baru untuk penggunaan internal (kerangka kerja di bawah platform SAP) dan kami semua dibagi menjadi dua kubu - pertama yang berpikir bahwa kerangka kerja harus memiliki akar global, dan yang kedua - yang berpikir sebaliknya.
Saya berada di kamp "akar global". Dan alasan saya apa pendekatan seperti itu akan menghasilkan fleksibilitas yang baik dan pengurangan biaya pengembangan menyebabkan kita tidak akan mengembangkan fungsionalitas umum lagi.
Jadi, saya sangat tertarik untuk mengetahui alasan apa yang mendorong arsitek .NET untuk mendesain kerangka kerja sedemikian rupa.
sumber
object
dalam kerangka .NET sebagian karena menyediakan beberapa kemampuan dasar di semua objek, sepertiToString()
danGetHashCode()
wait()
/notify()
/notifyAll()
danclone()
.Jawaban:
Penyebab yang paling mendesak
Object
adalah wadah (sebelum obat generik) yang dapat berisi apa saja alih-alih harus menggunakan gaya C "Tuliskan lagi untuk semua yang Anda butuhkan". Tentu saja, bisa dibilang, gagasan bahwa segala sesuatu harus mewarisi dari kelas tertentu dan kemudian menyalahgunakan fakta ini untuk benar-benar kehilangan setiap setitik tipe keselamatan begitu mengerikan sehingga seharusnya menjadi lampu peringatan raksasa untuk pengiriman bahasa tanpa obat generik, dan juga sarana ituObject
sepenuhnya berlebihan untuk kode baru.sumber
Object
yang tidak bisa ditulisvoid *
di C ++?void*
lebih baik? Bukan itu. Jika ada itu bahkan lebih buruk .void*
lebih buruk di C dengan semua cast pointer implisit, tetapi C ++ lebih ketat dalam hal ini. Setidaknya Anda harus berperan secara eksplisit.Saya ingat Eric Lippert pernah mengatakan bahwa mewarisi dari
System.Object
kelas memberikan 'nilai terbaik bagi pelanggan'. [edit: ya, katanya di sini ]:Memiliki semuanya berasal dari
System.Object
kelas memberikan keandalan dan kegunaan yang saya hormati. Saya tahu bahwa semua objek akan memiliki tipe (GetType
), bahwa mereka akan sejalan dengan metode finalisasi CLR (Finalize
), dan bahwa saya dapat menggunakannyaGetHashCode
pada mereka ketika berhadapan dengan koleksi.sumber
GetType
, Anda cukup memperpanjangtypeof
untuk mengganti fungsinya. AdapunFinalize
, kemudian sebenarnya, banyak jenis yang sama sekali tidak dapat diselesaikan sama sekali dan tidak memiliki metode Finalisasi yang berarti. Selain itu, banyak jenis yang tidak dapat hashable atau convertible ke string-terutama tipe internal yang tidak pernah dimaksudkan untuk hal seperti itu dan tidak pernah diberikan untuk penggunaan umum. Semua tipe ini memiliki antarmuka yang tercemar secara tidak perlu.GetType
,Finalize
atauGetHashCode
untuk setiapSystem.Object
. Saya hanya menyatakan bahwa mereka ada di sana jika Anda menginginkannya. Adapun "antarmuka mereka tercemar tanpa perlu", saya akan merujuk pada kutipan asli saya dari elippert: "nilai terbaik untuk pelanggan". Jelas tim .NET bersedia menangani pengorbanan.Saya berpikir bahwa desainer Java dan C # menambahkan objek root karena pada dasarnya gratis untuk mereka, seperti makan siang gratis .
Biaya menambahkan objek root sangat sama dengan biaya menambahkan fungsi virtual pertama Anda. Karena kedua CLR dan JVM adalah lingkungan yang dikumpulkan sampah dengan finalizer objek, Anda harus memiliki setidaknya satu virtual untuk Anda
java.lang.Object.finalize
atau untukSystem.Object.Finalize
metode Anda . Oleh karena itu, biaya untuk menambahkan objek root Anda sudah dibayar dimuka, Anda bisa mendapatkan semua manfaat tanpa membayarnya. Ini adalah yang terbaik dari kedua dunia: pengguna yang membutuhkan kelas root yang sama akan mendapatkan apa yang mereka inginkan, dan pengguna yang tidak peduli akan dapat memprogram seolah-olah itu tidak ada.sumber
Menurut saya Anda memiliki tiga pertanyaan di sini: satu adalah mengapa root umum diperkenalkan ke .NET, yang kedua adalah apa kelebihan dan kekurangan dari itu, dan yang ketiga adalah apakah ide yang baik untuk elemen kerangka kerja untuk memiliki global akar.
Mengapa .NET memiliki akar yang sama?
Dalam arti yang paling teknis, memiliki akar yang sama sangat penting untuk refleksi dan untuk wadah pra-generik.
Selain itu, setahu saya, memiliki akar yang sama dengan metode dasar seperti
equals()
danhashCode()
diterima dengan sangat positif di Jawa, dan C # dipengaruhi oleh (antara lain) Jawa, sehingga mereka ingin memiliki fitur itu juga.Apa kelebihan dan kekurangan dari memiliki akar yang sama dalam suatu bahasa?
Keuntungan memiliki akar yang sama:
equals()
danhashCode()
. Itu berarti, misalnya, bahwa setiap objek di Java atau C # dapat digunakan dalam peta hash - bandingkan situasi itu dengan C ++.printf
metode -seperti.object
. Mungkin tidak terlalu umum, tetapi tanpa akar kata yang sama, itu tidak mungkin.Kerugian memiliki akar yang sama:
Haruskah Anda menggunakan akar yang sama dalam proyek kerangka kerja Anda?
Sangat subyektif, tentu saja, tetapi ketika saya melihat daftar pro-con di atas saya pasti akan menjawab ya . Secara khusus, peluru terakhir dalam daftar pro - fleksibilitas kemudian mengubah perilaku semua objek dengan mengubah root - menjadi sangat berguna dalam platform, di mana perubahan ke root lebih mungkin diterima oleh klien daripada perubahan keseluruhan bahasa.
Selain itu - meskipun ini bahkan lebih subyektif - saya menemukan konsep root umum sangat elegan dan menarik. Ini juga membuat beberapa penggunaan alat lebih mudah, misalnya sekarang mudah untuk meminta alat untuk menunjukkan semua keturunan dari root yang sama dan menerima ikhtisar yang cepat dan bagus dari jenis kerangka kerja.
Tentu saja, tipe-tipe itu setidaknya harus sedikit terkait untuk itu, dan khususnya, saya tidak akan pernah mengorbankan aturan "is-a" hanya untuk memiliki akar yang sama.
sumber
Secara matematis, ini sedikit lebih elegan untuk memiliki sistem tipe yang berisi atas , dan memungkinkan Anda untuk mendefinisikan bahasa sedikit lebih lengkap.
Jika Anda membuat kerangka kerja, maka hal-hal tentu akan dikonsumsi oleh basis kode yang ada. Anda tidak dapat memiliki supertipe universal karena semua tipe lain dalam apa pun yang dikonsumsi kerangka ada.
Di bahwa titik, keputusan untuk memiliki kelas dasar umum tergantung pada apa yang Anda lakukan. Sangat jarang bahwa banyak hal memiliki perilaku yang sama, dan berguna untuk merujuk hal-hal ini melalui perilaku yang umum saja.
Tetapi itu terjadi. Jika ya, maka langsung saja abstraksi perilaku umum itu.
sumber
Mendorong untuk objek root karena mereka ingin semua kelas dalam kerangka mendukung hal-hal tertentu (mendapatkan kode hash, mengkonversi ke string, pemeriksaan kesetaraan, dll.). Baik C # dan Java merasa berguna untuk meletakkan semua fitur umum ini dari sebuah Object di beberapa kelas root.
Perlu diingat bahwa mereka tidak melanggar prinsip OOP atau apa pun. Apa pun di kelas Object masuk akal dalam subclass apa pun (baca: kelas apa saja ) dalam sistem. Jika Anda memilih untuk mengadopsi desain ini, pastikan Anda mengikuti pola ini. Artinya, jangan memasukkan hal-hal di root yang tidak termasuk dalam setiap, satu kelas di sistem Anda. Jika Anda mengikuti aturan ini dengan hati-hati, saya tidak melihat alasan mengapa Anda tidak boleh memiliki kelas root yang berisi kode umum yang berguna untuk seluruh sistem.
sumber
Beberapa alasan, fungsi umum yang bisa dibagi oleh semua kelas anak Dan itu juga memberi penulis kerangka kemampuan untuk menulis banyak fungsionalitas lain ke dalam kerangka yang semuanya bisa digunakan. Contohnya adalah caching dan sesi ASP.NET. Hampir semuanya dapat disimpan di dalamnya, karena mereka menulis metode add untuk menerima objek.
Kelas root bisa sangat memikat, tetapi sangat, sangat mudah disalahgunakan. Apakah mungkin untuk memiliki antarmuka root? Dan hanya punya satu atau dua metode kecil? Atau apakah itu akan menambah lebih banyak kode daripada yang dibutuhkan untuk kerangka kerja Anda?
Saya bertanya, saya ingin tahu tentang fungsi apa yang perlu Anda paparkan ke semua kelas yang mungkin dalam kerangka yang Anda tulis. Akankah itu benar-benar digunakan oleh semua benda? Atau oleh sebagian besar dari mereka? Dan begitu Anda membuat kelas root, bagaimana Anda mencegah orang menambahkan fungsionalitas acak ke dalamnya? Atau variabel acak yang mereka inginkan menjadi "global"?
Beberapa tahun yang lalu ada aplikasi yang sangat besar di mana saya membuat kelas root. Setelah beberapa bulan pengembangan itu diisi oleh kode yang tidak memiliki bisnis di sana. Kami mengonversi aplikasi ASP lama dan kelas root adalah pengganti file global.inc lama yang kami gunakan di masa lalu. Harus belajar pelajaran itu dengan cara yang sulit.
sumber
Semua benda tumpukan bawaan mewarisi dari
Object
; itu masuk akal karena semua objek tumpukan mandiri harus memiliki aspek umum tertentu, seperti cara mengidentifikasi tipenya. Kalau tidak, jika pengumpul sampah memiliki referensi ke objek tumpukan jenis yang tidak diketahui, itu tidak akan memiliki cara untuk mengetahui bit dalam gumpalan memori yang terkait dengan objek yang harus dianggap sebagai referensi ke objek tumpukan lainnya.Lebih jauh, dalam sistem tipe, akan lebih mudah untuk menggunakan mekanisme yang sama untuk mendefinisikan anggota struktur dan anggota kelas. Perilaku lokasi penyimpanan tipe nilai (variabel, parameter, bidang, slot array, dll.) Sangat berbeda dari lokasi penyimpanan tipe kelas, tetapi perbedaan perilaku tersebut dicapai dalam kompiler kode sumber dan mesin eksekusi (termasuk kompiler JIT) daripada diekspresikan dalam sistem tipe.
Salah satu konsekuensi dari ini adalah bahwa mendefinisikan tipe nilai secara efektif mendefinisikan dua tipe - tipe lokasi penyimpanan dan tipe objek tumpukan. Yang pertama dapat secara implisit dikonversi ke yang terakhir, dan yang terakhir dapat dikonversi ke yang pertama melalui typecast. Kedua jenis konversi ini berfungsi dengan menyalin semua bidang publik dan pribadi dari satu contoh dari jenis yang dipertanyakan. Selain itu, dimungkinkan menggunakan batasan umum untuk memanggil anggota antarmuka pada lokasi penyimpanan tipe nilai secara langsung, tanpa membuat salinannya terlebih dahulu.
Semua ini penting karena referensi ke objek tumpukan tipe nilai berperilaku seperti referensi kelas dan tidak suka tipe nilai. Pertimbangkan, misalnya, kode berikut:
Jika
testEnumerator()
metode ini meneruskan lokasi penyimpanan tipe nilai,it
akan menerima contoh yang bidang publik dan privatnya disalin dari nilai yang diteruskan. Variabel lokalit2
akan menyimpan instance lain yang bidangnya semua disalinit
. MemanggilMoveNext
padait2
tidak akan mempengaruhiit
.Jika kode di atas melewati lokasi penyimpanan tipe kelas, maka nilai yang diteruskan
it
,, danit2
, semua akan merujuk ke objek yang sama, dan dengan demikian memanggilMoveNext()
salah satu dari mereka secara efektif akan memanggilnya pada mereka semua.Perhatikan bahwa casting
List<String>.Enumerator
untukIEnumerator<String>
mengubahnya secara efektif dari tipe nilai ke tipe kelas. Tipe objek heap adalahList<String>.Enumerator
tetapi perilakunya akan sangat berbeda dari tipe nilai dari nama yang sama.sumber
List<T>.Enumerator
yang disimpan sebagai secaraList<T>.Enumerator
signifikan berbeda dari perilaku yang disimpan dalam suatuIEnumerator<T>
, di mana menyimpan nilai dari jenis sebelumnya ke lokasi penyimpanan dari kedua jenis akan membuat salinan dari keadaan pencacah, saat menyimpan nilai dari tipe yang terakhir ke lokasi penyimpanan yang juga dari tipe yang terakhir tidak akan membuat salinan.Object
memiliki tidak ada hubungannya dengan fakta bahwa.System.Int32
juga merupakan turunan dariSystem.Object
, tetapi lokasi penyimpanan tipeSystem.Int32
tidak memilikiSystem.Object
turunan maupun referensi ke satu.Desain ini benar-benar ditelusuri kembali ke Smalltalk, yang saya lihat sebagian besar sebagai upaya mengejar orientasi objek dengan mengorbankan hampir semua dan semua masalah lainnya. Karena itu, cenderung (menurut saya) untuk menggunakan orientasi objek, bahkan ketika teknik lain mungkin (atau bahkan pasti) lebih unggul.
Memiliki hierarki tunggal dengan
Object
(atau sesuatu yang serupa) di root membuatnya cukup mudah (untuk satu contoh) untuk membuat kelas koleksi Anda sebagai koleksiObject
, jadi sepele untuk koleksi berisi segala jenis objek.Sebagai imbalan untuk keuntungan yang agak kecil ini, Anda mendapatkan banyak kerugian. Pertama, dari sudut pandang desain, Anda berakhir dengan beberapa ide yang benar-benar gila. Setidaknya menurut pandangan Jawa tentang alam semesta, apa kesamaan Ateisme dan Hutan? Bahwa mereka berdua memiliki kode hash! Apakah Peta adalah koleksi? Menurut Jawa, tidak, bukan itu!
Pada 70-an ketika Smalltalk dirancang, omong kosong semacam ini diterima, terutama karena tidak ada yang merancang alternatif yang masuk akal. Smalltalk diselesaikan pada 1980, dan pada 1983 Ada (yang termasuk obat generik) dirancang. Meskipun Ada tidak pernah mencapai jenis popularitas yang diprediksi oleh beberapa orang, obat generiknya cukup untuk mendukung koleksi objek dari jenis yang sewenang-wenang - tanpa kegilaan yang melekat pada hierarki monolitik.
Ketika Java (dan pada tingkat lebih rendah, .NET) dirancang, hierarki kelas monolitik mungkin dipandang sebagai pilihan "aman" - satu dengan masalah, tetapi sebagian besar masalah diketahui . Pemrograman generik, sebaliknya, adalah sesuatu yang hampir semua orang (bahkan saat itu) sadari setidaknya secara teoritis merupakan pendekatan yang jauh lebih baik untuk masalah ini, tetapi yang oleh banyak pengembang berorientasi komersial dianggap agak kurang dieksplorasi dan / atau berisiko (yaitu, dalam dunia komersial , Ada sebagian besar ditolak karena gagal).
Biar saya perjelas: hierarki monolitik adalah kesalahan. Alasan untuk kesalahan itu setidaknya bisa dimengerti, tetapi itu adalah kesalahan. Ini desain yang buruk, dan masalah desainnya meliputi hampir semua kode yang menggunakannya.
Untuk desain baru hari ini, bagaimanapun, tidak ada pertanyaan yang masuk akal: menggunakan hierarki monolitik adalah kesalahan yang jelas dan ide yang buruk.
sumber
Apa yang ingin Anda lakukan sebenarnya menarik, sulit untuk membuktikan apakah itu salah atau benar sampai Anda selesai.
Berikut beberapa hal yang perlu dipikirkan:
Hanya itulah dua hal yang dapat diuntungkan dari root bersama. Dalam bahasa itu digunakan untuk mendefinisikan beberapa metode yang sangat umum dan memungkinkan Anda untuk melewati objek yang belum didefinisikan, tetapi itu tidak boleh berlaku untuk Anda.
Juga, dari pengalaman pribadi:
Saya menggunakan toolkit yang pernah ditulis oleh pengembang yang latar belakangnya adalah Smalltalk. Dia membuatnya sehingga semua "Data" kelasnya diperpanjang satu kelas dan semua metode mengambil kelas itu - sejauh ini sangat bagus. Masalahnya adalah kelas data yang berbeda tidak selalu dapat dipertukarkan, jadi sekarang editor dan kompiler saya tidak dapat memberi saya bantuan apa pun tentang apa yang harus diteruskan ke dalam situasi tertentu dan saya harus merujuk ke dokumennya yang tidak selalu membantu. .
Itu adalah perpustakaan yang paling sulit digunakan yang pernah saya tangani.
sumber
Karena itulah cara OOP. Sangat penting untuk memiliki tipe (objek) yang dapat merujuk pada apa saja. Argumen tipe objek dapat menerima apa pun. Ada juga warisan, Anda dapat memastikan bahwa ToString (), GetHashCode () dll tersedia pada apa saja dan segalanya.
Bahkan Oracle telah menyadari pentingnya memiliki tipe dasar seperti itu dan berencana untuk menghapus primitif di Jawa dekat 2017
sumber
ToString()
danGetType()
akan ada di setiap objek. Mungkin perlu menunjukkan bahwa Anda dapat menggunakan variabel tipeobject
untuk menyimpan objek .NET.