Saya sedang berdebat dengan teman saya mengenai apakah kedua latihan ini hanyalah dua sisi dari mata uang yang sama, atau apakah satu benar-benar lebih baik.
Kami memiliki fungsi yang mengambil parameter, mengisi anggota itu, dan kemudian mengembalikannya:
Item predictPrice(Item item)
Saya percaya bahwa saat ia bekerja pada objek yang sama yang dilewatkan, tidak ada gunanya mengembalikan item. Bahkan, jika ada, dari sudut pandang penelepon, itu membingungkan karena Anda bisa mengharapkannya untuk mengembalikan item baru yang tidak.
Dia mengklaim bahwa tidak ada bedanya, dan bahkan tidak masalah jika itu menciptakan Item baru dan mengembalikannya. Saya sangat tidak setuju, karena alasan berikut:
Jika Anda memiliki beberapa referensi ke item yang lewat (atau pointer atau apa pun), itu mengalokasikan objek baru dan mengembalikannya adalah penting secara materi karena referensi tersebut akan salah.
Dalam bahasa yang dikelola non-memori, fungsi yang mengalokasikan instance baru mengklaim kepemilikan memori, dan dengan demikian kita harus menerapkan metode pembersihan yang disebut di beberapa titik.
Mengalokasikan pada heap berpotensi mahal, dan karena itu penting untuk apakah fungsi yang dipanggil melakukan itu.
Oleh karena itu, saya percaya sangat penting untuk dapat melihat melalui metode tanda tangan apakah itu memodifikasi objek, atau mengalokasikan yang baru. Sebagai hasilnya, saya percaya bahwa karena fungsinya hanya memodifikasi objek yang dilewatkan, tanda tangannya harus:
void predictPrice(Item item)
Di setiap basis kode (diakui C dan C ++ basis kode, bukan Java yang merupakan bahasa tempat kami bekerja) saya telah bekerja dengan, gaya di atas pada dasarnya telah dipatuhi, dan dipelihara oleh programmer yang jauh lebih berpengalaman. Dia mengklaim bahwa ukuran sampel basis kode dan kolega saya kecil dari semua basis kode dan kolega yang mungkin, dan dengan demikian pengalaman saya bukanlah indikator yang benar tentang apakah seseorang lebih unggul.
Jadi, ada pemikiran?
sumber
Item
iniclass Item ...
dan tidaktypedef ...& Item
, lokalitem
sudah salinanJawaban:
Ini benar-benar masalah pendapat, tetapi untuk apa nilainya, saya merasa menyesatkan untuk mengembalikan barang yang dimodifikasi jika barang tersebut dimodifikasi di tempat. Secara terpisah, jika
predictPrice
akan memodifikasi item, item tersebut harus memiliki nama yang mengindikasikan akan melakukan hal itu (sukasetPredictPrice
atau semacamnya).Saya lebih suka (dalam urutan)
Itu
predictPrice
adalah metodeItem
(modulo alasan yang bagus untuk itu tidak ada, yang mungkin ada dalam desain keseluruhan Anda) yang baikMengembalikan harga yang diprediksi, atau
Punya nama seperti
setPredictedPrice
gantinyaItu
predictPrice
tidak berubahItem
, tetapi mengembalikan harga yang diprediksiItu
predictPrice
adalahvoid
metode yang disebutsetPredictedPrice
Itu
predictPrice
dikembalikanthis
(untuk metode chaining ke metode lain pada contoh apa pun itu adalah bagian dari) (dan dipanggilsetPredictedPrice
)sumber
a = doSomethingWith(b)
dimodifikasib
dan dikembalikan dengan modifikasi, yang meledakkan kode saya kemudian pada pemikiran yang tahu apa yangb
telah di dalamnya. :-)Dalam paradigma desain berorientasi objek, benda tidak boleh memodifikasi objek di luar objek itu sendiri. Setiap perubahan pada keadaan suatu objek harus dilakukan melalui metode pada objek.
Jadi,
void predictPrice(Item item)
sebagai fungsi anggota dari beberapa kelas lain salah . Bisa saja dapat diterima pada zaman C, tetapi untuk Java dan C ++, modifikasi suatu objek menyiratkan penggandaan yang lebih dalam pada objek yang kemungkinan akan menyebabkan masalah desain lainnya di jalan (ketika Anda refactor kelas dan mengubah bidangnya, sekarang 'predictPrice' perlu diubah ke dalam beberapa file lain.Mengembalikan objek baru, tidak memiliki efek samping terkait, parameter yang dikirimkan tidak diubah. Anda (metode predictPrice) tidak tahu di mana lagi parameter itu digunakan. Apakah
Item
kunci hash di suatu tempat? apakah Anda mengubah kode hash dalam melakukan ini? Apakah ada orang lain yang mengharapkannya agar tidak berubah?Masalah-masalah desain ini adalah masalah-masalah yang sangat menyarankan bahwa Anda tidak boleh memodifikasi objek (saya berpendapat dalam hal kekekalan dalam banyak kasus), dan jika Anda melakukannya, perubahan-perubahan pada keadaan harus dikendalikan dan dikendalikan oleh objek itu sendiri daripada sesuatu. lain di luar kelas.
Mari kita lihat apa yang terjadi jika seseorang mengotak-atik bidang sesuatu dalam hash. Mari kita ambil beberapa kode:
ideone
Dan saya akui bahwa ini bukan kode terhebat (secara langsung mengakses bidang), tetapi ini berfungsi untuk menunjukkan masalah dengan data yang dapat diubah digunakan sebagai kunci ke HashMap, atau dalam hal ini, hanya dimasukkan ke dalam HashSet.
Output dari kode ini adalah:
Apa yang terjadi adalah kode hash yang digunakan ketika dimasukkan adalah tempat objek berada di hash. Mengubah nilai yang digunakan untuk menghitung kode hash tidak menghitung ulang hash itu sendiri. Ini adalah bahaya menempatkan setiap objek bisa berubah sebagai kunci untuk hash.
Jadi, kembali ke aspek asli dari pertanyaan, metode yang dipanggil tidak "tahu" bagaimana objek itu mendapatkan sebagai parameter yang digunakan. Menyediakan "mari bermutasi objek" sebagai satu-satunya cara untuk melakukan ini berarti ada sejumlah bug halus yang dapat merayap masuk. Seperti kehilangan nilai dalam hash ... kecuali jika Anda menambahkan lebih banyak dan hash akan diulang kembali - percayalah padaku , itu bug jahat untuk diburu (saya kehilangan nilainya sampai saya menambahkan 20 item lagi ke hashMap dan kemudian tiba-tiba muncul lagi).
Terkait: Menimpa dan mengembalikan nilai argumen yang digunakan sebagai persyaratan pernyataan if, di dalam pernyataan if yang sama
sumber
predictPrice
tidak boleh secara langsung mengutak-atikItem
anggota, tetapi apa yang salah dengan metode panggilanItem
yang melakukannya? Jika itu adalah satu-satunya objek yang sedang dimodifikasi, mungkin Anda dapat membuat argumen bahwa itu harus menjadi metode objek yang sedang dimodifikasi, tetapi di lain waktu beberapa objek (yang tentunya tidak boleh dari kelas yang sama) sedang dimodifikasi, terutama di metode tingkat tinggi.Saya selalu mengikuti praktik tidak mengubah objek orang lain. Dengan kata lain, hanya bermutasi objek yang dimiliki oleh kelas / struct / yang Anda miliki di dalam.
Diberikan kelas data berikut:
Ini bagus:
Dan ini sangat jelas:
Tapi ini terlalu berlumpur dan misterius untuk seleraku:
sumber
Sintaks berbeda antara bahasa yang berbeda, dan tidak ada artinya untuk menunjukkan sepotong kode yang tidak spesifik untuk bahasa karena itu berarti hal yang sama sekali berbeda dalam bahasa yang berbeda.
Di Jawa,
Item
adalah tipe referensi - itu adalah jenis pointer ke objek yang merupakan contohItem
. Dalam C ++, tipe ini ditulis sebagaiItem *
(sintaks untuk hal yang sama berbeda antara bahasa). JadiItem predictPrice(Item item)
di Jawa sama denganItem *predictPrice(Item *item)
di C ++. Dalam kedua kasus, itu adalah fungsi (atau metode) yang mengambil pointer ke objek, dan mengembalikan pointer ke objek.Dalam C ++,
Item
(tanpa apa pun) adalah tipe objek (dengan asumsiItem
adalah nama kelas). Nilai dari tipe ini "adalah" objek, danItem predictPrice(Item item)
mendeklarasikan fungsi yang mengambil objek dan mengembalikan objek. Karena&
tidak digunakan, itu dilewatkan dan dikembalikan dengan nilai. Itu berarti objek disalin ketika melewati, dan disalin ketika kembali. Tidak ada padanan di Java karena Java tidak memiliki tipe objek.Jadi yang mana? Banyak hal yang Anda tanyakan dalam pertanyaan (mis. "Saya percaya bahwa ketika ia bekerja pada objek yang sama dengan yang dilewati ...") tergantung pada pemahaman persis apa yang sedang berlalu dan dikembalikan ke sini.
sumber
Kebiasaan yang saya lihat dari basis kode adalah untuk memilih gaya yang jelas dari sintaksis. Jika fungsi berubah nilainya, lebih suka lewat pointer
Gaya ini menghasilkan:
Sebut saja Anda lihat:
predictPrice (& my_item);
dan Anda tahu itu akan dimodifikasi, oleh kepatuhan Anda terhadap gaya.
sumber
Meskipun saya tidak memiliki preferensi yang kuat, saya akan memilih bahwa mengembalikan objek mengarah pada fleksibilitas yang lebih baik untuk API.
Perhatikan bahwa itu hanya masuk akal sementara metode memiliki kebebasan untuk mengembalikan objek lain. Jika javadoc Anda mengatakan
@returns the same value of parameter a
itu tidak ada gunanya. Tetapi jika javadoc Anda mengatakan@return an instance that holds the same data than parameter a
, maka mengembalikan instance yang sama atau yang lain adalah detail implementasi.Misalnya, dalam proyek saya saat ini (Java EE) saya memiliki lapisan bisnis; untuk menyimpan dalam DB (dan menetapkan ID otomatis) seorang Karyawan, katakanlah, seorang karyawan saya memiliki sesuatu seperti
Tentu saja, tidak ada perbedaan dengan implementasi ini karena JPA mengembalikan objek yang sama setelah menetapkan ID. Sekarang, jika saya beralih ke JDBC
Sekarang jam ????? Saya punya tiga opsi.
Tentukan setter untuk Id, dan saya akan mengembalikan objek yang sama. Jelek, karena
setId
akan tersedia di mana-mana.Lakukan beberapa pekerjaan refleksi berat dan atur id di
Employee
objek. Kotor, tetapi setidaknya itu terbatas padacreateEmployee
catatan.Berikan konstruktor yang menyalin yang asli
Employee
dan menerima jugaid
untuk mengatur.Ambil objek dari DB (mungkin diperlukan jika beberapa nilai bidang dihitung dalam DB, atau jika Anda ingin mengisi hubungan realtionship).
Sekarang, untuk 1. dan 2. Anda mungkin mengembalikan instance yang sama, tetapi untuk 3. dan 4. Anda akan mengembalikan instance baru. Jika dari prinsip Anda menetapkan dalam API Anda bahwa nilai "nyata" akan menjadi yang dikembalikan oleh metode, Anda memiliki lebih banyak kebebasan.
Satu-satunya argumen nyata yang dapat saya pikirkan adalah bahwa, dengan menggunakan implementatios 1. atau 2., orang yang menggunakan API Anda mungkin mengabaikan penetapan hasil dan kode mereka akan rusak jika Anda berubah menjadi 3. atau 4. implementasi).
Bagaimanapun, seperti yang Anda lihat, preferensi saya didasarkan pada preferensi pribadi lainnya (seperti melarang setter untuk Id), jadi mungkin itu tidak berlaku untuk semua orang.
sumber
Saat melakukan pengkodean di Java, kita harus mencoba membuat penggunaan setiap objek dengan tipe yang bisa berubah sesuai dengan satu dari dua pola:
Satu entitas yang dianggap sebagai "pemilik" objek yang dapat berubah dan menganggap status objek sebagai bagian dari miliknya. Entitas lain mungkin memegang referensi, tetapi harus menganggap referensi sebagai mengidentifikasi objek yang dimiliki oleh orang lain.
Meskipun objek dapat berubah, tidak ada orang yang memegang referensi padanya diizinkan untuk memutasinya, sehingga membuat instance secara efektif tidak dapat diubah (perhatikan bahwa karena kebiasaan dalam model memori Java, tidak ada cara yang baik untuk membuat objek yang dapat diubah secara efektif memiliki thread- semantik abadi yang aman tanpa menambahkan tingkat tipuan ekstra untuk setiap akses). Setiap entitas yang merangkum negara menggunakan referensi ke objek seperti itu dan ingin mengubah negara yang dienkapsulasi harus membuat objek baru yang berisi keadaan yang tepat.
Saya tidak suka memiliki metode bermutasi objek yang mendasarinya dan mengembalikan referensi, karena mereka memberikan penampilan yang pas dengan pola kedua di atas. Ada beberapa kasus di mana pendekatannya mungkin membantu, tetapi kode yang akan bermutasi objek akan terlihat seperti itu akan melakukannya; kode seperti
myThing = myThing.xyz(q);
terlihat jauh lebih sedikit seperti itu bermutasi objek yang diidentifikasimyThing
daripada hanyamyThing.xyz(q);
.sumber