Sebagai contoh, saya telah melihat beberapa kode yang membuat fragmen seperti ini:
Fragment myFragment=new MyFragment();
yang mendeklarasikan variabel sebagai Fragmen alih-alih MyFragment, yang MyFragment adalah kelas anak dari Fragment. Saya tidak puas dengan baris kode ini karena saya pikir kode ini seharusnya:
MyFragment myFragment=new MyFragment();
mana yang lebih spesifik, apakah itu benar?
Atau dalam generalisasi pertanyaan, apakah praktik yang buruk untuk digunakan:
Parent x=new Child();
dari pada
Child x=new Child();
jika kita dapat mengubah yang sebelumnya menjadi yang terakhir tanpa kompilasi kesalahan?
Parent x = new Child()
adalah ide yang bagus.Jawaban:
Tergantung pada konteksnya, tetapi saya berpendapat Anda harus menyatakan jenis yang paling abstrak . Dengan begitu kode Anda akan menjadi se-general mungkin dan tidak bergantung pada detail yang tidak relevan.
Sebuah contoh akan memiliki
LinkedList
danArrayList
yang berasal dari keduanyaList
. Jika kode akan berfungsi sama baiknya dengan daftar jenis apa pun maka tidak ada alasan untuk secara sewenang-wenang membatasi ke salah satu subkelas.sumber
List list = new ArrayList()
, Kode yang menggunakan daftar tidak peduli apa jenis daftar itu, hanya yang memenuhi kontrak antarmuka Daftar. Di masa mendatang, kami dapat dengan mudah mengubah ke implementasi Daftar yang berbeda dan kode yang mendasarinya tidak perlu diubah atau diperbarui sama sekali.Object
!Jawaban JacquesB benar, meskipun sedikit abstrak. Biarkan saya menekankan beberapa aspek dengan lebih jelas:
Dalam cuplikan kode Anda, Anda secara eksplisit menggunakan
new
kata kunci, dan mungkin Anda ingin melanjutkan dengan langkah-langkah inisialisasi lebih lanjut yang mungkin spesifik untuk jenis beton: maka Anda perlu mendeklarasikan variabel dengan jenis beton tertentu.Situasinya berbeda ketika Anda mendapatkan item itu dari tempat lain, misalnya
IFragment fragment = SomeFactory.GiveMeFragments(...);
. Di sini, pabrik biasanya akan mengembalikan jenis yang paling abstrak yang mungkin, dan kode ke bawah tidak boleh bergantung pada detail implementasi.sumber
Ada potensi perbedaan kinerja.
Jika kelas turunan disegel, maka kompiler dapat sebaris dan mengoptimalkan atau menghilangkan apa yang seharusnya panggilan virtual. Jadi bisa ada perbedaan kinerja yang signifikan.
Contoh:
ToString
adalah panggilan virtual, tetapiString
disegel. AktifString
,ToString
adalah no-op, jadi jika Anda mendeklarasikan sebagaiobject
panggilan virtual, jika Anda menyatakanString
kompiler tahu tidak ada kelas turunan yang menimpa metode karena kelas disegel, maka itu adalah no-op. Pertimbangan serupa berlaku untukArrayList
vsLinkedList
.Oleh karena itu, jika Anda mengetahui tipe konkret objek, (dan tidak ada alasan enkapsulasi untuk menyembunyikannya), Anda harus mendeklarasikan sebagai tipe itu. Karena Anda baru saja membuat objek, Anda tahu tipe konkret.
sumber
Parent p = new Child()
selalu anak-anak ..Perbedaan utama adalah tingkat akses yang diperlukan. Dan setiap jawaban yang baik tentang abstraksi melibatkan hewan, atau begitulah yang saya katakan.
Biarkan kami mengira Anda memiliki beberapa hewan -
Cat
Bird
danDog
. Sekarang, hewan ini memiliki perilaku umum beberapa -move()
,eat()
, danspeak()
. Mereka semua makan secara berbeda dan berbicara dengan cara yang berbeda, tetapi jika saya membutuhkan hewan saya untuk makan atau berbicara, saya tidak terlalu peduli bagaimana mereka melakukannya.Tetapi terkadang saya melakukannya. Saya tidak pernah memiliki kucing penjaga atau burung penjaga, tetapi saya memiliki anjing penjaga. Jadi ketika seseorang mendobrak masuk ke rumah saya, saya tidak bisa hanya mengandalkan berbicara untuk menakuti pengganggu. Saya benar-benar membutuhkan anjing saya untuk menggonggong - ini berbeda dari pidatonya, yang hanya guk.
Jadi dalam kode yang membutuhkan penyusup saya benar-benar perlu melakukannya
Dog fido = getDog(); fido.barkMenancingly();
Tapi sebagian besar waktu, saya bisa dengan senang hati membuat hewan melakukan trik di mana mereka makan, mengeong atau tweet untuk hadiah.
Animal pet = getAnimal(); pet.speak();
Jadi agar lebih konkret, ArrayList memiliki beberapa metode yang
List
tidak. KhususnyatrimToSize()
,. Sekarang, dalam 99% kasus, kami tidak peduli tentang ini. ItuList
hampir tidak pernah cukup besar bagi kita untuk peduli. Tetapi ada saat-saat seperti itu. Jadi kadang-kadang, kita harus secara khusus meminta untukArrayList
mengeksekusitrimToSize()
dan membuat array dukungannya lebih kecil.Perhatikan bahwa ini tidak harus dilakukan saat konstruksi - itu bisa dilakukan melalui metode pengembalian atau bahkan pemeran jika kami benar-benar yakin.
sumber
Secara umum, Anda harus menggunakan
atau
untuk variabel lokal kecuali ada alasan kuat untuk variabel tersebut memiliki tipe yang berbeda seperti apa yang diinisialisasi dengannya.
(Di sini saya mengabaikan fakta bahwa kode C ++ kemungkinan besar harus menggunakan smart pointer. Ada beberapa kasus yang tidak dan tanpa lebih dari satu baris saya tidak bisa benar-benar tahu.)
Gagasan umum di sini adalah dengan bahasa yang mendukung deteksi tipe variabel otomatis, menggunakannya membuat kode lebih mudah dibaca dan melakukan hal yang benar (yaitu, sistem tipe kompiler dapat mengoptimalkan sebanyak mungkin dan mengubah inisialisasi menjadi yang baru tipe berfungsi juga jika sebagian besar abstrak telah digunakan).
sumber
Bagus karena mudah dimengerti dan mudah untuk memodifikasi kode ini:
Bagus karena itu menyampaikan maksud:
Ok, karena itu umum dan mudah dimengerti:
Satu opsi yang benar-benar buruk adalah menggunakan
Parent
jika hanyaChild
memiliki metodeDoSomething()
. Itu buruk karena itu mengomunikasikan niat salah:Sekarang mari kita uraikan sedikit lebih banyak pada kasus pertanyaan yang secara khusus ditanyakan tentang:
Mari kita format ini secara berbeda, dengan membuat panggilan fungsi paruh kedua:
Ini dapat disingkat menjadi:
Di sini, saya menganggap itu diterima secara luas yang
WhichType
seharusnya menjadi tipe paling dasar / abstrak yang memungkinkan fungsi untuk bekerja (kecuali ada masalah kinerja). Dalam hal ini tipe yang tepat adalah salah satuParent
, atauParent
berasal dari sesuatu .Alur penalaran ini menjelaskan mengapa menggunakan jenis
Parent
ini adalah pilihan yang baik, tetapi itu tidak masuk ke cuaca pilihan lain yang buruk (mereka tidak).sumber
tl; dr - Menggunakan
Child
lebihParent
disukai dalam lingkup lokal. Ini tidak hanya membantu keterbacaan, tetapi juga perlu untuk memastikan bahwa resolusi metode kelebihan beban bekerja dengan baik dan membantu memungkinkan kompilasi yang efisien.Dalam lingkup lokal,
Secara konseptual, ini tentang menjaga informasi jenis yang paling mungkin. Jika kami menurunkan versi ke
Parent
, kami pada dasarnya hanya menghapus informasi jenis yang bisa berguna.Mempertahankan informasi tipe yang lengkap memiliki empat keunggulan utama:
Keuntungan 1: Informasi lebih lanjut ke kompiler
Jenis yang jelas digunakan dalam resolusi metode kelebihan beban dan optimasi.
Contoh: Resolusi metode kelebihan beban
Dalam contoh di atas, kedua
foo()
panggilan berfungsi , tetapi dalam satu kasus, kami mendapatkan resolusi metode kelebihan beban yang lebih baik.Contoh: Optimasi kompiler
Dalam contoh di atas, kedua
.Foo()
panggilan pada akhirnya memanggiloverride
metode yang sama yang mengembalikan2
. Hanya saja, dalam kasus pertama, ada pencarian metode virtual untuk menemukan metode yang benar; pencarian metode virtual ini tidak diperlukan dalam kasus kedua sejak metode itusealed
.Kredit untuk @Ben yang memberikan contoh serupa dalam jawabannya .
Keuntungan 2: Informasi lebih lanjut kepada pembaca
Mengetahui jenis yang tepat, yaitu
Child
, memberikan lebih banyak informasi kepada siapa pun yang membaca kode, sehingga lebih mudah untuk melihat apa yang sedang dilakukan program.Tentu, mungkin tidak peduli dengan kode aktual karena kedua
parent.Foo();
danchild.Foo();
masuk akal, tapi bagi seseorang melihat kode untuk pertama kalinya, informasi lebih lanjut hanya biasa membantu.Selain itu, tergantung pada lingkungan pengembangan Anda, IDE mungkin dapat memberikan tooltips lebih bermanfaat dan metadata tentang
Child
dibandingParent
.Keuntungan 3: Lebih bersih, kode lebih standar
Sebagian besar contoh kode C # yang saya lihat akhir-akhir ini digunakan
var
, yang pada dasarnya adalah singkatanChild
.Melihat
var
pernyataan non- deklarasi sama sekali tidak terlihat; jika ada alasan situasional untuk itu, mengagumkan, tetapi sebaliknya itu terlihat anti-pola.Keuntungan 4: Lebih banyak logika program yang bisa berubah untuk prototyping
Tiga keuntungan pertama adalah yang besar; ini jauh lebih situasional.
Namun, untuk orang yang menggunakan kode seperti orang lain menggunakan Excel, kami terus mengubah kode kami. Mungkin kita tidak perlu memanggil metode unik untuk
Child
di ini versi kode, tapi kami mungkin menggunakan kembali atau ulang kode nanti.Keuntungan dari sistem tipe yang kuat adalah bahwa ia memberi kami meta-data tertentu tentang logika program kami, membuat kemungkinan lebih mudah terlihat. Ini sangat berguna dalam pembuatan prototipe, jadi yang terbaik adalah simpan sedapat mungkin.
Ringkasan
Menggunakan
Parent
resolusi metode overloaded yang berantakan, menghambat beberapa optimisasi kompiler, menghapus informasi dari pembaca, dan membuat kode lebih jelek.Menggunakan
var
benar-benar cara untuk pergi. Cepat, bersih, berpola, dan membantu kompiler dan IDE untuk melakukan pekerjaan mereka dengan benar.Penting : Jawaban ini tentang
Parent
vs.Child
dalam lingkup lokal metode. MasalahParent
vsChild
sangat berbeda untuk jenis kembali, argumen, dan bidang kelas.sumber
Parent list = new Child()
itu tidak masuk akal. Kode yang secara langsung membuat turunan konkret dari suatu objek (yang bertentangan dengan tipuan melalui pabrik, metode pabrik dll.) Memiliki informasi yang sempurna tentang jenis yang sedang dibuat, mungkin perlu berinteraksi dengan antarmuka konkret untuk mengkonfigurasi objek yang baru dibuat dll. Lingkup lokal bukanlah tempat Anda mencapai fleksibilitas. Fleksibilitas tercapai ketika Anda mengekspos objek yang baru dibuat ke klien kelas Anda menggunakan antarmuka yang lebih abstrak (pabrik dan metode pabrik adalah contoh sempurna)Parent p = new Child(); p.doSomething();
danChild c = new Child; c.doSomething();
memiliki perilaku yang berbeda? Mungkin itu adalah kekhasan dalam beberapa bahasa, tetapi seharusnya objek mengontrol perilaku, bukan referensi. Itulah keindahan warisan! Selama Anda bisa mempercayai implementasi anak untuk memenuhi kontrak implementasi orangtua, Anda baik untuk melakukannya.new
kata kunci dalam C # , dan ketika ada banyak definisi, misalnya dengan pewarisan berganda di C ++ .Parent
adalahinterface
.Diberikan
parentType foo = new childType1();
, kode akan dibatasi untuk menggunakan metode tipe induk padafoo
, tetapi akan dapat menyimpan kefoo
referensi ke objek apa pun yang jenisnya berasal dari -parent
bukan hanya contohchildType1
. JikachildType1
instance baru adalah satu-satunya yangfoo
pernah memiliki referensi, maka mungkin lebih baik untuk menyatakanfoo
tipe sebagaichildType1
. Di sisi lain, jikafoo
mungkin perlu menyimpan referensi ke tipe lain, setelah itu dinyatakan sebagaiparentType
akan memungkinkan.sumber
Saya sarankan menggunakan
dari pada
Yang terakhir kehilangan informasi sementara yang pertama tidak.
Anda dapat melakukan segalanya dengan yang pertama yang bisa Anda lakukan dengan yang terakhir tetapi tidak sebaliknya.
Untuk menguraikan pointer lebih jauh, mari Anda memiliki beberapa level pewarisan.
Base
->DerivedL1
->DerivedL2
Dan Anda ingin menginisialisasi hasil
new DerivedL2()
ke variabel. Menggunakan tipe dasar memberi Anda pilihan untuk menggunakanBase
atauDerivedL1
.Anda dapat gunakan
atau
Saya tidak melihat cara logis untuk lebih memilih satu daripada yang lain.
Jika Anda menggunakan
tidak ada yang perlu diperdebatkan.
sumber
Base
(dengan asumsi itu berfungsi). Anda lebih suka yang paling spesifik, AFAIK mayoritas lebih suka yang paling spesifik. Saya akan mengatakan bahwa untuk variabel lokal tidak masalah.Base x = new DerivedL2();
maka Anda paling dibatasi dan ini memungkinkan Anda untuk mengganti anak (cucu) lain dengan upaya minimum. Selain itu, Anda segera melihat bahwa itu mungkin. +++ Apakah kami setuju bahwavoid f(Base)
itu lebih baik daripadavoid f(DerivedL2)
?Saya akan menyarankan selalu mengembalikan atau menyimpan variabel sebagai tipe yang paling spesifik, tetapi menerima parameter sebagai tipe terluas.
Misalnya
Parameternya adalah
List<T>
, tipe yang sangat umum.ArrayList<T>
,LinkedList<T>
dan yang lainnya semuanya dapat diterima dengan metode ini.Yang penting, tipe pengembaliannya
LinkedHashMap<K, V>
bukanMap<K, V>
. Jika seseorang ingin menugaskan hasil metode ini keMap<K, V>
, mereka dapat melakukannya. Ini dengan jelas mengomunikasikan bahwa hasil ini lebih dari sekedar peta, tetapi peta dengan urutan yang ditentukan.Misalkan metode ini dikembalikan
Map<K, V>
. Jika penelepon menginginkanLinkedHashMap<K, V>
, mereka harus melakukan pemeriksaan jenis, pemeran dan penanganan kesalahan, yang benar-benar rumit.sumber
Map
bukanLinkedHashMap
- Anda hanya ingin mengembalikanLinkedHashMap
untuk fungsi sepertikeyValuePairsToFifoMap
atau sesuatu. Lebih luas lebih baik sampai Anda memiliki alasan khusus untuk tidak melakukannya.LinkedHashMap
untukTreeMap
apa pun alasannya, sekarang aku punya banyak hal untuk perubahan.LinkedHashMap
menjadiTreeMap
bukan perubahan sepele, seperti mengubah dariArrayList
menjadiLinkedList
. Ini adalah perubahan dengan kepentingan semantik. Dalam hal ini Anda ingin kompiler untuk melakukan kesalahan, untuk memaksa konsumen dari nilai pengembalian untuk memperhatikan perubahan dan mengambil tindakan yang sesuai.Thread#getAllStackTraces()
- mengembalikanMap<Thread,StackTraceElement[]>
bukanHashMap
khusus sehingga di jalan mereka dapat mengubahnya ke jenis apa pun yangMap
mereka inginkan.