Saya sering mengalami masalah ini, terutama di Jawa, bahkan jika saya pikir itu adalah masalah OOP umum. Yaitu: menaikkan pengecualian mengungkapkan masalah desain.
Misalkan saya memiliki kelas yang memiliki String name
bidang dan String surname
bidang.
Kemudian ia menggunakan bidang-bidang itu untuk menulis nama lengkap seseorang untuk menampilkannya pada semacam dokumen, misalnya faktur.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Sekarang saya ingin memperkuat perilaku kelas saya dengan melemparkan kesalahan jika displayCompleteNameOnInvoice
dipanggil sebelum nama telah ditetapkan. Sepertinya ide yang bagus, bukan?
Saya dapat menambahkan kode yang meningkatkan pengecualian ke getCompleteName
metode. Tetapi dengan cara ini saya melanggar kontrak 'implisit' dengan pengguna kelas; pada umumnya getter tidak seharusnya melempar pengecualian jika nilainya tidak disetel. Ok, ini bukan pengambil standar karena tidak mengembalikan satu bidang, tetapi dari sudut pandang pengguna perbedaannya mungkin terlalu halus untuk dipikirkan.
Atau saya bisa melempar pengecualian dari dalam displayCompleteNameOnInvoice
. Tetapi untuk melakukannya saya harus menguji secara langsung name
atau surname
bidang dan melakukannya saya akan melanggar abstraksi yang diwakili oleh getCompleteName
. Metode ini bertanggung jawab untuk memeriksa dan membuat nama lengkap. Bahkan bisa memutuskan, mendasarkan keputusan pada data lain, bahwa dalam beberapa kasus itu cukup surname
.
Jadi satu-satunya kemungkinan tampaknya mengubah semantik metode getCompleteName
untuk composeCompleteName
, yang menunjukkan lebih 'aktif' perilaku dan, dengan itu, kemampuan melempar pengecualian.
Apakah ini solusi desain yang lebih baik? Saya selalu mencari keseimbangan terbaik antara kesederhanaan dan kebenaran. Apakah ada referensi desain untuk masalah ini?
sumber
displayCompleteNameOnInvoice
bisa saja melempar pengecualian jikagetCompleteName
kembalinull
, bukan?Jawaban:
Jangan izinkan kelas Anda dibangun tanpa memberikan nama.
sumber
Jawaban yang diberikan oleh DeadMG cukup kuat, tetapi izinkan saya mengatakannya sedikit berbeda: Hindari memiliki benda dengan keadaan tidak valid. Objek harus "utuh" dalam konteks tugas yang dipenuhi. Atau seharusnya tidak ada.
Jadi jika Anda ingin fluiditas, gunakan sesuatu seperti Pola Builder . Atau hal lain yang merupakan reifikasi terpisah dari suatu objek dalam konstruksi yang bertentangan dengan "hal yang nyata", di mana yang terakhir dijamin memiliki keadaan yang valid dan merupakan salah satu yang benar-benar memperlihatkan operasi yang didefinisikan pada keadaan itu (misalnya
getCompleteName
).sumber
So if you want fluidity, use something like the builder pattern
- fluiditas, atau kekekalan (kecuali itu yang Anda maksudkan entah bagaimana). Jika seseorang ingin membuat objek yang tidak dapat diubah, tetapi perlu menunda pengaturan beberapa nilai, maka pola yang harus diikuti adalah mengaturnya satu per satu pada objek pembangun dan hanya membuat objek yang tidak berubah setelah kami mengumpulkan semua nilai yang diperlukan untuk yang benar negara ...Tidak memiliki objek dengan keadaan tidak valid.
Tujuan konstruktor atau pembangun adalah untuk mengatur keadaan objek menjadi sesuatu yang konsisten dan dapat digunakan. Tanpa jaminan ini, masalah muncul dengan keadaan inisialisasi objek yang tidak benar. Masalah ini menjadi diperparah jika Anda berurusan dengan konkurensi dan sesuatu dapat mengakses objek sebelum Anda selesai mengaturnya.
Jadi, pertanyaan untuk bagian ini adalah "mengapa Anda membiarkan nama atau nama keluarga menjadi nol?" Jika itu bukan sesuatu yang valid di kelas agar bisa berfungsi dengan baik, jangan izinkan. Memiliki konstruktor atau pembangun yang memvalidasi pembuatan objek dengan benar dan jika tidak benar, angkat masalah pada titik itu. Anda juga dapat menggunakan salah satu
@NotNull
anotasi yang ada untuk membantu mengomunikasikan bahwa "ini tidak boleh nol" dan menerapkannya dalam pengkodean dan analisis.Dengan jaminan ini di tempat, menjadi lebih mudah untuk alasan tentang kode, apa yang dilakukannya, dan tidak harus membuang pengecualian di tempat-tempat aneh atau melakukan pemeriksaan berlebihan di sekitar fungsi pengambil.
Getters yang melakukan lebih banyak.
Ada sedikit di luar sana tentang hal ini. Anda sudah mendapatkan Berapa Banyak Logika dalam Getters dan Apa yang harus diizinkan di dalam getter dan setter? dari sini dan getter dan setter melakukan logika tambahan pada Stack Overflow. Ini adalah masalah yang muncul berulang kali dalam desain kelas.
Inti dari ini berasal dari:
Dan kamu benar. Seharusnya tidak. Mereka harus terlihat 'bodoh' ke seluruh dunia dan melakukan apa yang diharapkan. Menempatkan terlalu banyak logika di sana mengarah ke masalah di mana hukum yang paling mengejutkan dilanggar. Dan mari kita hadapi itu, Anda benar-benar tidak ingin membungkus getter dengan
try catch
karena kita tahu betapa kita suka melakukan itu secara umum.Ada juga situasi di mana Anda harus menggunakan
getFoo
metode seperti kerangka JavaBeans dan ketika Anda memiliki sesuatu dari panggilan EL yang mengharapkan mendapatkan kacang (sehingga<% bar.foo %>
benar - benar memanggilgetFoo()
di kelas - mengesampingkan 'harus kacang harus melakukan komposisi atau harus yang dibiarkan begitu saja? 'karena seseorang dapat dengan mudah menemukan situasi di mana satu atau yang lainnya jelas dapat menjadi jawaban yang tepat)Sadar juga bahwa adalah mungkin bagi seorang pengambil yang diberikan untuk ditentukan dalam antarmuka atau telah menjadi bagian dari API publik yang sebelumnya terbuka untuk kelas yang mendapatkan refactored (versi sebelumnya hanya memiliki 'completeName' yang dikembalikan dan sebuah refactoring pecah menjadi dua bidang).
Pada akhir hari...
Lakukan hal yang paling mudah untuk dipikirkan. Anda akan menghabiskan lebih banyak waktu mempertahankan kode ini daripada menghabiskan waktu mendesainnya (meskipun semakin sedikit waktu yang Anda habiskan untuk mendesain, semakin banyak waktu yang Anda habiskan untuk pemeliharaannya). Pilihan yang ideal adalah mendesain sedemikian rupa sehingga tidak akan memakan banyak waktu untuk mempertahankannya, tetapi jangan duduk di sana memikirkannya selama berhari-hari - kecuali jika ini benar-benar merupakan pilihan desain yang akan berimplikasi beberapa hari kemudian .
Mencoba untuk tetap dengan kemurnian semantik dari makhluk pengambil
private T foo; T getFoo() { return foo; }
akan membuat Anda mendapat masalah di beberapa titik. Terkadang kode itu tidak sesuai dengan model itu dan liuk yang dilalui seseorang untuk membuatnya tetap seperti itu tidak masuk akal ... dan pada akhirnya membuatnya lebih sulit untuk dirancang.Kadang-kadang terima bahwa cita-cita desain tidak dapat diwujudkan seperti yang Anda inginkan dalam kode. Pedoman adalah pedoman - bukan belenggu.
sumber
surname
atauname
null adalah keadaan yang valid untuk objek, maka tangani sesuai itu. Tetapi jika itu bukan keadaan yang valid - menyebabkan metode di dalam kelas untuk melempar pengecualian, itu harus dicegah berada dalam keadaan itu dari titik itu diinisialisasi. Anda bisa mengedarkan objek yang tidak lengkap, tetapi mengedarkan objek yang tidak valid bermasalah. Jika nama nol adalah luar biasa, cobalah untuk mencegahnya lebih awal daripada nanti.Saya bukan orang Jawa, tetapi ini tampaknya mematuhi kedua kendala yang Anda sajikan.
getCompleteName
tidak melempar pengecualian jika nama tidak diinisialisasi, dandisplayCompleteNameOnInvoice
tidak.sumber
getCompleteName()
tidak perlu peduli apakah properti itu benar-benar disimpan atau jika itu dihitung dengan cepat.getCompleteName
, yang mengerikan.getCompleteName()
sisa kelas, atau bahkan di bagian lain dari program. Dalam beberapa kasus, pengembaliannull
mungkin persis apa yang diperlukan. Tetapi sementara ada SATU metode yang specnya "melempar pengecualian dalam kasus ini", itu PERSIS apa yang harus kita kode.Sepertinya tidak ada yang menjawab pertanyaan Anda.
Tidak seperti apa yang orang suka percaya, "menghindari benda yang tidak valid" tidak sering merupakan solusi praktis.
Jawabannya adalah:
Ya seharusnya.
Contoh mudah:
FileStream.Length
dalam C # /. NET , yang melemparNotSupportedException
jika file tidak dapat dicari.sumber
Anda memiliki seluruh nama memeriksa terbalik.
getCompleteName()
tidak harus tahu fungsi mana yang akan memanfaatkannya. Dengan demikian, tidak memilikiname
saat memanggildisplayCompleteNameOnInvoice()
bukangetCompleteName()
tanggung jawab.Tapi,
name
merupakan prasyarat yang jelas untukdisplayCompleteNameOnInvoice()
. Dengan demikian, ia harus mengambil tanggung jawab untuk memeriksa negara (Atau harus mendelegasikan tanggung jawab untuk memeriksa ke metode lain, jika Anda mau).sumber