Dalam kode lama saya kadang-kadang melihat kelas yang tidak lain adalah pembungkus untuk data. sesuatu seperti:
class Bottle {
int height;
int diameter;
Cap capType;
getters/setters, maybe a constructor
}
Pemahaman saya tentang OO adalah bahwa kelas adalah struktur untuk data dan metode operasi pada data itu. Ini sepertinya menghalangi objek dari tipe ini. Bagi saya mereka tidak lebih dari structs
dan semacam mengalahkan tujuan OO. Saya tidak berpikir itu pasti jahat, meskipun itu mungkin bau kode.
Apakah ada kasus di mana benda seperti itu diperlukan? Jika ini sering digunakan, apakah itu membuat desain curiga?
java
code-quality
object-oriented
code-smell
Michael K.
sumber
sumber
Jawaban:
Jelas bukan kejahatan dan bukan bau kode di pikiranku. Wadah data adalah warga negara OO yang valid. Terkadang Anda ingin merangkum informasi terkait bersama. Jauh lebih baik memiliki metode seperti
dari
Menggunakan kelas juga memungkinkan Anda untuk menambahkan parameter tambahan ke Botol tanpa mengubah setiap pemanggil dari DoStuffWithBottle. Dan Anda dapat mensubklasifikasikan Botol dan selanjutnya meningkatkan keterbacaan dan pengaturan kode Anda, jika perlu.
Ada juga objek data biasa yang dapat dikembalikan sebagai hasil dari kueri basis data, misalnya. Saya percaya istilah untuk mereka dalam hal ini adalah "Objek Transfer Data".
Dalam beberapa bahasa ada pertimbangan lain juga. Misalnya, dalam C # kelas dan struct berperilaku berbeda, karena struct adalah tipe nilai dan kelas adalah tipe referensi.
sumber
DoStuffWith
harus menjadi metode dari yangBottle
kelas dalam OOP (dan harus paling mungkin berubah, juga). Apa yang Anda tulis di atas bukanlah pola yang baik dalam bahasa OO (kecuali Anda berinteraksi dengan API lama). Ini adalah , bagaimanapun, desain berlaku di lingkungan non-OO.Kelas data valid dalam beberapa kasus. DTO adalah salah satu contoh bagus yang disebutkan oleh Anna Lear. Namun secara umum, Anda harus menganggap mereka sebagai benih kelas yang metodenya belum tumbuh. Dan jika Anda menemukan banyak dari mereka dalam kode lama, perlakukan mereka sebagai bau kode yang kuat. Mereka sering digunakan oleh programmer C / C ++ lama yang tidak pernah membuat transisi ke pemrograman OO dan merupakan tanda pemrograman prosedural. Mengandalkan getter dan setter setiap saat (atau lebih buruk lagi, pada akses langsung anggota non-pribadi) dapat membuat Anda mendapat masalah sebelum Anda menyadarinya. Pertimbangkan contoh metode eksternal yang membutuhkan informasi
Bottle
.Berikut
Bottle
ini adalah kelas data):Di sini kami telah memberikan
Bottle
beberapa perilaku):Metode pertama melanggar prinsip Tell-Don't-Ask, dan dengan menjaga
Bottle
kebodohan kita telah membiarkan pengetahuan implisit tentang botol, seperti apa yang membuat seseorang rapuhCap
, tergelincir ke dalam logika yang berada di luarBottle
kelas. Anda harus waspada untuk mencegah 'kebocoran' semacam ini ketika Anda terbiasa mengandalkan getter.Metode kedua
Bottle
hanya meminta apa yang dibutuhkan untuk melakukan tugasnya, dan pergiBottle
untuk memutuskan apakah itu rapuh, atau lebih besar dari ukuran yang diberikan. Hasilnya adalah kopling yang jauh lebih longgar antara metode dan implementasi Botol. Efek samping yang menyenangkan adalah metode ini lebih bersih dan lebih ekspresif.Anda jarang akan menggunakan banyak bidang objek ini tanpa menulis beberapa logika yang harus berada di kelas dengan bidang tersebut. Cari tahu apa logika itu dan kemudian pindahkan ke tempat yang seharusnya.
sumber
Jika ini adalah jenis hal yang Anda butuhkan, itu tidak masalah, tapi tolong, tolong, tolong lakukan
bukannya sesuatu seperti
Silahkan.
sumber
Seperti yang dikatakan @Anna, jelas tidak jahat. Tentu Anda bisa memasukkan operasi (metode) ke dalam kelas, tetapi hanya jika Anda mau . Anda tidak harus melakukannya.
Izinkan saya sedikit keluhan tentang gagasan bahwa Anda harus memasukkan operasi ke dalam kelas, bersama dengan gagasan bahwa kelas adalah abstraksi. Dalam praktiknya, ini mendorong programmer untuk
Buat lebih banyak kelas daripada yang mereka butuhkan (struktur data yang berlebihan). Ketika struktur data mengandung lebih banyak komponen daripada yang diperlukan seminimal mungkin, itu tidak dinormalisasi, dan karena itu mengandung keadaan tidak konsisten. Dengan kata lain, ketika diubah, itu perlu diubah di lebih dari satu tempat agar tetap konsisten. Kegagalan untuk melakukan semua perubahan terkoordinasi membuatnya tidak konsisten, dan merupakan bug.
Selesaikan masalah 1 dengan memasukkan metode pemberitahuan , sehingga jika bagian A dimodifikasi, ia mencoba untuk menyebarkan perubahan yang diperlukan ke bagian B dan C. Ini adalah alasan utama mengapa disarankan untuk memiliki metode aksesor get-and-set. Karena ini adalah praktik yang disarankan, tampaknya memaafkan masalah 1, menyebabkan lebih banyak masalah 1 dan lebih banyak solusi 2. Hasil ini tidak hanya pada bug karena mengimplementasikan notifikasi yang tidak lengkap, tetapi juga pada masalah pengisapan kinerja notifikasi runaway. Ini bukan perhitungan yang tak terbatas, hanya yang sangat panjang.
Konsep-konsep ini diajarkan sebagai hal yang baik, umumnya oleh para guru yang tidak harus bekerja dalam jutaan aplikasi monster yang penuh dengan masalah ini.
Inilah yang saya coba lakukan:
Pertahankan data dinormalisasi mungkin, sehingga ketika perubahan dilakukan pada data, hal itu dilakukan pada titik kode sesedikit mungkin, untuk meminimalkan kemungkinan memasuki keadaan yang tidak konsisten.
Ketika data harus dinormalisasi, dan redundansi tidak dapat dihindarkan, jangan gunakan pemberitahuan dalam upaya untuk membuatnya konsisten. Sebaliknya, toleransilah terhadap inkonsistensi sementara. Atasi ketidakkonsistenan dengan sapuan berkala melalui data dengan proses yang hanya melakukan itu. Ini memusatkan tanggung jawab untuk menjaga konsistensi, sambil menghindari masalah kinerja dan kebenaran yang rentan terhadap pemberitahuan. Ini menghasilkan kode yang jauh lebih kecil, bebas kesalahan, dan efisien.
sumber
Kelas semacam ini cukup berguna ketika Anda berurusan dengan aplikasi ukuran menengah / besar, untuk beberapa alasan:
Singkatnya, menurut pengalaman saya, mereka biasanya lebih bermanfaat daripada menyebalkan.
sumber
Dengan desain game, overhead 1000 fungsi panggilan, dan pendengar acara kadang-kadang membuatnya layak untuk memiliki kelas yang hanya menyimpan data, dan memiliki kelas lain yang mengulang semua data hanya kelas untuk melakukan logika.
sumber
Setuju dengan Anna Lear,
Kadang-kadang orang lupa membaca Konvensi Java Coding 1999 yang membuatnya sangat jelas bahwa pemrograman semacam ini baik-baik saja. Bahkan jika Anda menghindarinya, maka kode Anda akan tercium! (terlalu banyak getter / setter)
Dari Konvensi Kode Java 1999: Salah satu contoh variabel instance publik yang sesuai adalah kasus di mana kelas pada dasarnya adalah struktur data, tanpa perilaku. Dengan kata lain, jika Anda akan menggunakan struct bukan kelas (jika Java didukung struct), maka itu tepat untuk membuat variabel instance kelas publik. http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177
Ketika digunakan dengan benar, POD (struktur data lama biasa) lebih baik daripada POJO seperti POJO sering lebih baik daripada EJB.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures
sumber
Bangunan memiliki tempat mereka, bahkan di Jawa. Anda hanya boleh menggunakannya jika dua hal berikut ini benar:
Jika ini masalahnya, maka Anda harus membuat bidang publik dan melewati getter / setter. Lagipula Getters dan setter kikuk, dan Java konyol karena tidak memiliki properti seperti bahasa yang berguna. Karena objek struct-like Anda seharusnya tidak memiliki metode apa pun, bidang publik paling masuk akal.
Namun, jika salah satu dari itu tidak berlaku, Anda sedang berhadapan dengan kelas nyata. Itu berarti semua bidang harus bersifat pribadi. (Jika Anda benar-benar membutuhkan bidang pada cakupan yang lebih mudah diakses, gunakan pengambil / penyetel.)
Untuk memeriksa apakah seharusnya struktur Anda memiliki perilaku, lihat ketika bidang digunakan. Jika tampaknya melanggar katakan, jangan tanya , maka Anda harus memindahkan perilaku itu ke dalam kelas Anda.
Jika beberapa data Anda tidak boleh berubah, maka Anda harus membuat semua bidang itu final. Anda mungkin mempertimbangkan untuk membuat kelas Anda tidak berubah . Jika Anda perlu memvalidasi data Anda, maka berikan validasi di setter dan konstruktor. (Trik yang berguna adalah mendefinisikan setter pribadi dan memodifikasi bidang Anda di dalam kelas Anda hanya menggunakan setter itu.)
Contoh Botol Anda kemungkinan besar akan gagal dalam kedua pengujian. Anda dapat memiliki (buat) kode yang terlihat seperti ini:
Sebaliknya seharusnya
Jika Anda mengubah tinggi dan diameter, apakah itu botol yang sama? Mungkin tidak. Itu harus final. Apakah nilai negatif ok untuk diameter? Haruskah Botol Anda lebih tinggi dari lebar? Bisakah Cap menjadi nol? Tidak? Bagaimana Anda memvalidasi ini? Anggaplah klien itu bodoh atau jahat. ( Tidak mungkin untuk mengatakan perbedaannya. ) Anda perlu memeriksa nilai-nilai ini.
Seperti inilah bentuk kelas Botol Anda yang baru:
sumber
IMHO sering tidak ada kelas yang cukup seperti ini dalam sistem berorientasi objek. Saya perlu hati-hati untuk memenuhi syarat itu.
Tentu saja jika bidang data memiliki cakupan dan jarak pandang yang luas, itu bisa sangat tidak diinginkan jika ada ratusan atau ribuan tempat di basis kode Anda yang merusak data tersebut. Itu meminta masalah dan kesulitan mempertahankan invarian. Namun pada saat yang sama, itu tidak berarti bahwa setiap kelas di seluruh basis kode mendapat manfaat dari penyembunyian informasi.
Tetapi ada banyak kasus di mana bidang data seperti itu akan memiliki cakupan yang sangat sempit. Contoh yang sangat mudah adalah
Node
kelas privat dari struktur data. Sering dapat menyederhanakan kode banyak dengan mengurangi jumlah interaksi objek yang terjadi jika dikatakanNode
hanya bisa terdiri dari data mentah. Itu berfungsi sebagai mekanisme decoupling karena versi alternatif mungkin memerlukan kopling dua arah dari, katakanlah,Tree->Node
danNode->Tree
bukan hanyaTree->Node Data
.Contoh yang lebih kompleks adalah sistem entitas-komponen seperti yang sering digunakan dalam mesin game. Dalam kasus tersebut, komponen seringkali hanya berupa data mentah dan kelas seperti yang Anda tunjukkan. Namun, ruang lingkup / visibilitas mereka cenderung terbatas karena biasanya hanya ada satu atau dua sistem yang dapat mengakses jenis komponen tertentu. Akibatnya, Anda masih cenderung menemukan cukup mudah untuk mempertahankan invarian dalam sistem tersebut dan, apalagi, sistem seperti itu memiliki sedikit
object->object
interaksi, sehingga sangat mudah untuk memahami apa yang terjadi pada level mata burung.Dalam kasus seperti itu, Anda dapat berakhir dengan sesuatu yang lebih seperti ini sejauh interaksi berjalan (diagram ini menunjukkan interaksi, bukan kopling, karena diagram kopling mungkin menyertakan antarmuka abstrak untuk gambar kedua di bawah):
... berbeda dengan ini:
... dan jenis sistem yang sebelumnya cenderung lebih mudah untuk dipelihara dan beralasan dalam hal kebenaran terlepas dari kenyataan bahwa dependensi sebenarnya mengalir ke data. Anda mendapatkan lebih sedikit kopling terutama karena banyak hal dapat diubah menjadi data mentah, bukan objek yang berinteraksi satu sama lain membentuk grafik interaksi yang sangat kompleks.
sumber
Bahkan ada banyak makhluk aneh yang ada di dunia OOP. Saya pernah membuat kelas, yang abstrak, dan tidak mengandung apa-apa, itu hanya untuk menjadi induk dari dua kelas lainnya ... ini adalah kelas Port:
Jadi SceneMember adalah kelas dasar, ia melakukan semua pekerjaan, yang diperlukan untuk muncul di tempat kejadian: menambah, menghapus, mengatur ID, dll. Pesan adalah koneksi antar port, ia memiliki kehidupannya sendiri. InputPort dan OutputPort berisi fungsi spesifik i / o sendiri.
Port kosong. Ini hanya digunakan untuk mengelompokkan InputPort dan OutputPort bersama untuk, katakanlah, sebuah portlist. Itu kosong karena SceneMember berisi semua hal untuk bertindak sebagai anggota, dan InputPort dan OutputPort berisi tugas yang ditentukan port. InputPort dan OutputPort sangat berbeda, sehingga tidak memiliki fungsi umum (kecuali SceneMember). Jadi, Port kosong. Tapi ini legal.
Mungkin ini adalah antipemutaran , tetapi saya menyukainya.
(Ini seperti kata "jodoh", yang digunakan untuk "istri" dan "suami". Anda tidak pernah menggunakan kata "jodoh" untuk orang konkret, karena Anda tahu jenis kelaminnya, dan itu tidak masalah jika dia sudah menikah atau belum, maka Anda menggunakan "seseorang" sebagai gantinya, tetapi itu masih ada, dan digunakan dalam situasi yang jarang dan abstrak, misalnya teks hukum.)
sumber