Apakah "Induk x = Anak baru ();" bukannya "Anak x = Anak baru ();" praktik yang buruk jika kita dapat menggunakan yang terakhir?

32

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?

ggrr
sumber
6
Jika kita melakukan yang terakhir, tidak ada gunanya abstraksi / generalisasi lagi. Tentu saja ada pengecualian ...
Walfrat
2
Dalam proyek yang sedang saya kerjakan, fungsi saya mengharapkan tipe induk, dan menjalankan metode tipe induk. Saya dapat menerima salah satu dari beberapa anak, dan kode di belakang metode (yang diwariskan dan diganti) berbeda, tetapi saya ingin menjalankan kode itu tidak peduli anak mana yang saya gunakan.
Stephen S
4
@ Walfrat Memang ada sedikit gunanya abstraksi ketika Anda sudah menanamkan tipe beton, tetapi Anda masih bisa mengembalikan tipe abstrak sehingga kode klien sangat bodoh.
Jacob Raihle
4
Jangan gunakan Orang Tua / Anak. Gunakan Antarmuka / Kelas (untuk Java).
Thorbjørn Ravn Andersen
4
Google "memprogram ke antarmuka" untuk sekumpulan penjelasan tentang mengapa menggunakan Parent x = new Child()adalah ide yang bagus.
Kevin Workman

Jawaban:

69

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 LinkedListdan ArrayListyang berasal dari keduanya List. Jika kode akan berfungsi sama baiknya dengan daftar jenis apa pun maka tidak ada alasan untuk secara sewenang-wenang membatasi ke salah satu subkelas.

JacquesB
sumber
24
Persis. Untuk menambahkan contoh, pikirkan 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.
Maybe_Factor
19
jawaban yang baik tetapi harus memperluas jenis abstrak yang paling mungkin berarti bahwa kode dalam ruang lingkup tidak boleh dilemparkan kembali ke anak untuk melakukan beberapa operasi khusus anak.
Mike
10
@ Mike: Saya harap itu tidak perlu dikatakan, jika tidak semua variabel, parameter dan bidang akan dinyatakan sebagai Object!
JacquesB
3
@ JacquesB: karena pertanyaan dan jawaban ini menerima begitu banyak perhatian saya berpikir bahwa menjadi eksplisit akan bermanfaat bagi orang-orang dengan semua tingkat keahlian yang berbeda.
Mike
1
Itu tergantung pada konteksnya bukan jawaban.
Billal Begueradj
11

Jawaban JacquesB benar, meskipun sedikit abstrak. Biarkan saya menekankan beberapa aspek dengan lebih jelas:

Dalam cuplikan kode Anda, Anda secara eksplisit menggunakan newkata 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.

Bernhard Hiller
sumber
18
"Meskipun sedikit abstrak". Badumtish.
rosuav
1
tidak bisakah ini hasil edit?
beppe9000
6

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: ToStringadalah panggilan virtual, tetapi Stringdisegel. Aktif String, ToStringadalah no-op, jadi jika Anda mendeklarasikan sebagai objectpanggilan virtual, jika Anda menyatakan Stringkompiler tahu tidak ada kelas turunan yang menimpa metode karena kelas disegel, maka itu adalah no-op. Pertimbangan serupa berlaku untuk ArrayListvs LinkedList.

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.

Ben
sumber
4
Dalam kebanyakan kasus, perbedaan kinerja sangat kecil. Dan dalam kebanyakan kasus (saya pernah mendengar bahwa ini adalah sekitar 90% dari waktu) kita harus melupakan perbedaan kecil itu. Hanya ketika kita tahu (lebih disukai melalui pengukuran) bahwa kita menua sebagai bagian kode kinerja kritis yang harus kita peduli dengan optimasi tersebut.
Jules
Dan kompiler tahu bahwa "Anak baru ()" mengembalikan Anak, bahkan ketika ditugaskan ke Orang Tua, dan selama ia tahu, ia dapat menggunakan pengetahuan itu dan memanggil kode Anak ().
gnasher729
+1. Juga mencuri contoh Anda dalam jawaban saya. = P
Nat
1
Perhatikan bahwa ada optimisasi kinerja yang membuat semuanya diperdebatkan. HotSpot misalnya akan melihat bahwa parameter yang diteruskan ke fungsi selalu dari satu kelas tertentu dan mengoptimalkannya. Jika asumsi itu ternyata salah, metode ini akan dioptimalkan. Tetapi ini sangat tergantung pada lingkungan kompiler / runtime. Terakhir kali saya memeriksa CLR bahkan tidak bisa melakukan optimasi yang agak sepele dari memperhatikan bahwa p dari Parent p = new Child()selalu anak-anak ..
Voo
4

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 Birddan Dog. Sekarang, hewan ini memiliki perilaku umum beberapa - move(), eat(), dan speak(). 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 Listtidak. Khususnya trimToSize(),. Sekarang, dalam 99% kasus, kami tidak peduli tentang ini. Itu Listhampir tidak pernah cukup besar bagi kita untuk peduli. Tetapi ada saat-saat seperti itu. Jadi kadang-kadang, kita harus secara khusus meminta untuk ArrayListmengeksekusi trimToSize()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.

corsiKa
sumber
Bingung mengapa ini mungkin mendapatkan downvote. Tampaknya kanonik, secara pribadi. Kecuali untuk ide anjing penjaga yang dapat diprogram ...
corsiKa
Mengapa saya harus membaca halaman, untuk mendapatkan jawaban terbaik? Rating menyebalkan.
Basilevs
Terima kasih atas kata-kata baiknya. Ada pro dan kontra untuk sistem peringkat, dan salah satunya adalah bahwa jawaban nanti kadang-kadang bisa ditinggalkan dalam debu. Itu terjadi!
corsiKa
2

Secara umum, Anda harus menggunakan

var x = new Child(); // C#

atau

auto x = new Child(); // C++

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).

Joshua
sumber
1
Di C ++, kebanyakan variabel dibuat tanpa kata kunci baru: stackoverflow.com/questions/6500313/…
Marian Spanik
1
Pertanyaannya bukan tentang C # / C ++ tetapi tentang masalah berorientasi objek umum dalam bahasa yang memiliki setidaknya beberapa bentuk pengetikan statis (yaitu, tidak masuk akal untuk bertanya dalam sesuatu seperti Ruby). Selain itu, bahkan dalam dua bahasa itu, saya tidak benar-benar melihat alasan yang baik mengapa kita harus melakukannya seperti yang Anda katakan.
AnoE
1

Bagus karena mudah dimengerti dan mudah untuk memodifikasi kode ini:

var x = new Child(); 
x.DoSomething();

Bagus karena itu menyampaikan maksud:

Parent x = new Child(); 
x.DoSomething(); 

Ok, karena itu umum dan mudah dimengerti:

Child x = new Child(); 
x.DoSomething(); 

Satu opsi yang benar-benar buruk adalah menggunakan Parentjika hanya Childmemiliki metode DoSomething(). Itu buruk karena itu mengomunikasikan niat salah:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

Sekarang mari kita uraikan sedikit lebih banyak pada kasus pertanyaan yang secara khusus ditanyakan tentang:

Parent x = new Child(); 
x.DoSomething(); 

Mari kita format ini secara berbeda, dengan membuat panggilan fungsi paruh kedua:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

Ini dapat disingkat menjadi:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

Di sini, saya menganggap itu diterima secara luas yang WhichTypeseharusnya menjadi tipe paling dasar / abstrak yang memungkinkan fungsi untuk bekerja (kecuali ada masalah kinerja). Dalam hal ini tipe yang tepat adalah salah satu Parent, atau Parentberasal dari sesuatu .

Alur penalaran ini menjelaskan mengapa menggunakan jenis Parentini adalah pilihan yang baik, tetapi itu tidak masuk ke cuaca pilihan lain yang buruk (mereka tidak).

Peter
sumber
1

tl; dr - MenggunakanChildlebihParentdisukai 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,

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

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:

  1. Memberikan informasi lebih lanjut kepada kompiler.
  2. Memberikan lebih banyak informasi kepada pembaca.
  3. Kode lebih bersih dan lebih standar.
  4. Membuat logika program lebih bisa berubah.

Keuntungan 1: Informasi lebih lanjut ke kompiler

Jenis yang jelas digunakan dalam resolusi metode kelebihan beban dan optimasi.

Contoh: Resolusi metode kelebihan beban

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

Dalam contoh di atas, kedua foo()panggilan berfungsi , tetapi dalam satu kasus, kami mendapatkan resolusi metode kelebihan beban yang lebih baik.

Contoh: Optimasi kompiler

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

Dalam contoh di atas, kedua .Foo()panggilan pada akhirnya memanggil overridemetode yang sama yang mengembalikan 2. 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 itu sealed.

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();dan child.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 Childdibanding Parent.

Keuntungan 3: Lebih bersih, kode lebih standar

Sebagian besar contoh kode C # yang saya lihat akhir-akhir ini digunakan var, yang pada dasarnya adalah singkatan Child.

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

Melihat varpernyataan non- deklarasi sama sekali tidak terlihat; jika ada alasan situasional untuk itu, mengagumkan, tetapi sebaliknya itu terlihat anti-pola.

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

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 Childdi 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 Parentresolusi metode overloaded yang berantakan, menghambat beberapa optimisasi kompiler, menghapus informasi dari pembaca, dan membuat kode lebih jelek.

Menggunakan varbenar-benar cara untuk pergi. Cepat, bersih, berpola, dan membantu kompiler dan IDE untuk melakukan pekerjaan mereka dengan benar.


Penting : Jawaban ini tentangParentvs.Childdalam lingkup lokal metode. MasalahParentvsChildsangat berbeda untuk jenis kembali, argumen, dan bidang kelas.

Nat
sumber
2
Saya akan menambahkan bahwa 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)
crizzis
"tl; dr Menggunakan Child over Parent lebih disukai dalam lingkup lokal. Ini tidak hanya membantu keterbacaan," - belum tentu ... jika Anda tidak menggunakan spesifik anak, maka itu hanya akan menambahkan rincian lebih dari yang diperlukan. "tetapi juga perlu untuk memastikan bahwa resolusi metode kelebihan beban bekerja dengan baik dan membantu memungkinkan kompilasi yang efisien." - Itu sangat tergantung pada bahasa pemrograman. Ada banyak yang tidak memiliki masalah apa pun yang timbul dari ini.
AnoE
1
Apakah Anda memiliki sumber itu Parent p = new Child(); p.doSomething();dan Child 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.
corsiKa
@corsiKa Ya, itu mungkin dalam beberapa cara. Dua contoh cepat adalah ketika tanda tangan metode ditimpa, mis. Dengan newkata kunci dalam C # , dan ketika ada banyak definisi, misalnya dengan pewarisan berganda di C ++ .
Nat
@corsiKa Hanya contoh ketiga yang cepat (karena saya pikir ini kontras dengan C ++ dan C #), C # memiliki masalah seperti C ++, di mana Anda juga bisa mendapatkan perilaku ini dari metode antarmuka eksplisit . Ini lucu karena bahasa seperti C # menggunakan antarmuka bukannya multiple-inheritance sebagian untuk menghindari masalah ini, tetapi dengan mengadopsi antarmuka, mereka masih mendapat bagian dari masalah - tetapi, itu tidak terlalu buruk karena, dalam skenario itu, hanya berlaku kapan Parentadalah interface.
Nat
0

Diberikan parentType foo = new childType1();, kode akan dibatasi untuk menggunakan metode tipe induk pada foo, tetapi akan dapat menyimpan ke fooreferensi ke objek apa pun yang jenisnya berasal dari - parentbukan hanya contoh childType1. Jika childType1instance baru adalah satu-satunya yang foopernah memiliki referensi, maka mungkin lebih baik untuk menyatakan footipe sebagai childType1. Di sisi lain, jika foomungkin perlu menyimpan referensi ke tipe lain, setelah itu dinyatakan sebagai parentTypeakan memungkinkan.

supercat
sumber
0

Saya sarankan menggunakan

Child x = new Child();

dari pada

Parent x = new Child();

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 menggunakan Baseatau DerivedL1.

Anda dapat gunakan

Base x = new DerivedL2();

atau

DerivedL1 x = new DerivedL2();

Saya tidak melihat cara logis untuk lebih memilih satu daripada yang lain.

Jika Anda menggunakan

DerivedL2 x = new DerivedL2();

tidak ada yang perlu diperdebatkan.

R Sahu
sumber
3
Mampu berbuat lebih sedikit jika Anda tidak perlu melakukan lebih banyak adalah hal yang baik, bukan?
Peter
1
@ Peter, kemampuan untuk melakukan lebih sedikit tidak dibatasi. Itu selalu mungkin. Jika kemampuan untuk melakukan lebih banyak terkendala, itu mungkin merupakan titik pahit.
R Sahu
Tetapi kehilangan informasi terkadang merupakan hal yang baik. Mengenai Anda hierarki, kebanyakan orang mungkin akan memilih 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.
maaartinus
@maaartinus, saya terkejut mayoritas pengguna lebih suka kehilangan informasi. Saya tidak melihat manfaat kehilangan informasi sejelas orang lain.
R Sahu
Ketika Anda mendeklarasikan 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 bahwa void f(Base)itu lebih baik daripada void f(DerivedL2)?
maaartinus
0

Saya akan menyarankan selalu mengembalikan atau menyimpan variabel sebagai tipe yang paling spesifik, tetapi menerima parameter sebagai tipe terluas.

Misalnya

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

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>bukan Map<K, V>. Jika seseorang ingin menugaskan hasil metode ini ke Map<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 menginginkan LinkedHashMap<K, V>, mereka harus melakukan pemeriksaan jenis, pemeran dan penanganan kesalahan, yang benar-benar rumit.

Alexander - Pasang kembali Monica
sumber
Logika yang sama yang memaksa Anda untuk menerima tipe luas sebagai parameter juga harus berlaku untuk tipe pengembalian juga. Jika tujuan dari fungsi ini adalah untuk memetakan kunci ke nilai, itu harus mengembalikan Mapbukan LinkedHashMap- Anda hanya ingin mengembalikan LinkedHashMapuntuk fungsi seperti keyValuePairsToFifoMapatau sesuatu. Lebih luas lebih baik sampai Anda memiliki alasan khusus untuk tidak melakukannya.
corsiKa
@corsiKa Hmm Saya setuju itu nama yang lebih baik, tapi ini hanya satu contoh. Saya tidak setuju bahwa (secara umum) memperluas jenis pengembalian Anda lebih baik. Anda menghapus informasi jenis secara artifisial, tanpa alasan apa pun. Jika Anda membayangkan pengguna Anda secara wajar perlu membuang kembali tipe luas yang Anda berikan kepada mereka, maka Anda telah melakukan tindakan yang merugikan mereka.
Alexander - Pasang kembali Monica
"tanpa pembenaran" - tetapi ada pembenaran! Jika saya bisa lolos dengan mengembalikan jenis yang lebih luas itu membuat refactoring lebih mudah di jalan. Jika seseorang MEMBUTUHKAN jenis tertentu karena beberapa perilaku di atasnya, saya semua untuk mengembalikan jenis yang sesuai. Tetapi saya tidak ingin dikunci ke dalam tipe tertentu jika saya tidak harus terkunci pada saya. Saya ingin bebas untuk mengubah spesifik dari metode saya tanpa merugikan mereka yang mengkonsumsinya, dan jika saya mengubahnya dari, mengatakan LinkedHashMapuntuk TreeMapapa pun alasannya, sekarang aku punya banyak hal untuk perubahan.
corsiKa
Sebenarnya, saya setuju dengan itu sampai kalimat terakhir. Anda berubah dari LinkedHashMapmenjadi TreeMapbukan perubahan sepele, seperti mengubah dari ArrayListmenjadi LinkedList. 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.
Alexander - Reinstate Monica
Tapi itu bisa menjadi perubahan sepele jika yang Anda pedulikan adalah perilaku peta (yang, jika Anda bisa, Anda harus.) Jika kontraknya adalah "Anda akan mendapatkan peta nilai kunci" dan pesanan tidak masalah (yang berlaku untuk kebanyakan peta) maka tidak masalah apakah itu pohon atau peta hash. Misalnya, Thread#getAllStackTraces()- mengembalikan Map<Thread,StackTraceElement[]>bukan HashMapkhusus sehingga di jalan mereka dapat mengubahnya ke jenis apa pun yang Mapmereka inginkan.
corsiKa