Ide dasar di balik OOP adalah bahwa data dan perilaku (berdasarkan data itu) tidak dapat dipisahkan dan mereka digabungkan oleh gagasan objek kelas. Objek memiliki data dan metode yang bekerja dengan itu (dan data lainnya). Jelas dengan prinsip-prinsip OOP, objek yang hanya data (seperti C struct) dianggap sebagai anti-pola.
Sejauh ini bagus.
Masalahnya adalah saya telah memperhatikan bahwa kode saya tampaknya akan semakin ke arah pola-anti ini akhir-akhir ini. Tampak bagi saya bahwa semakin saya mencoba untuk mendapatkan informasi yang bersembunyi di antara kelas-kelas dan desain yang digabungkan secara longgar, semakin banyak kelas saya yang menjadi campuran data murni tanpa kelas perilaku dan semua perilaku tanpa kelas data.
Saya biasanya merancang kelas dengan cara yang meminimalkan kesadaran mereka tentang keberadaan kelas lain dan meminimalkan pengetahuan mereka tentang antarmuka kelas lain. Saya terutama menegakkan ini dengan cara top-down, kelas tingkat bawah tidak tahu tentang kelas tingkat yang lebih tinggi. Misalnya:
Misalkan Anda memiliki API permainan kartu umum. Anda memiliki kelas Card
. Sekarang Card
kelas ini perlu menentukan visibilitas ke pemain.
Salah satunya adalah dengan memiliki boolean isVisible(Player p)
di Card
kelas.
Lain adalah memiliki boolean isVisible(Card c)
di Player
kelas.
Saya tidak menyukai pendekatan pertama khususnya karena memberikan pengetahuan tentang Player
kelas tingkat yang lebih tinggi ke kelas tingkat yang lebih rendah Card
.
Sebagai gantinya saya memilih opsi ketiga di mana kita memiliki Viewport
kelas yang, diberikan Player
dan daftar kartu menentukan kartu mana yang terlihat.
Namun pendekatan ini merampas baik Card
dan Player
kelas dari fungsi anggota yang mungkin. Setelah Anda melakukan ini untuk hal-hal lain selain visibilitas kartu, Anda dibiarkan dengan Card
dan Player
kelas - kelas yang berisi data murni karena semua fungsi diimplementasikan di kelas-kelas lain, yang sebagian besar kelas tanpa data, hanya metode, seperti di Viewport
atas.
Ini jelas bertentangan dengan gagasan utama OOP.
Mana cara yang benar? Bagaimana saya harus pergi tentang tugas meminimalkan interdependensi kelas dan meminimalkan asumsi pengetahuan dan penggabungan, tetapi tanpa menutup dengan desain aneh di mana semua kelas tingkat rendah hanya berisi data dan kelas tingkat tinggi berisi semua metode? Adakah yang punya solusi atau perspektif ketiga tentang desain kelas yang menghindari seluruh masalah?
PS Berikut contoh lain:
Misalkan Anda memiliki kelas DocumentId
yang tidak berubah, hanya memiliki satu BigDecimal id
anggota dan pengambil untuk anggota ini. Sekarang Anda perlu memiliki metode di suatu tempat, yang memberikan DocumentId
pengembalian Document
untuk id ini dari database.
Apakah kamu:
- Tambahkan
Document getDocument(SqlSession)
metode keDocumentId
kelas, tiba-tiba memperkenalkan pengetahuan tentang kegigihan Anda ("we're using a database and this query is used to retrieve document by id"
), API yang digunakan untuk mengakses DB dan sejenisnya. Juga kelas ini sekarang membutuhkan file JAR persistensi hanya untuk mengkompilasi. - Tambahkan beberapa kelas lain dengan metode
Document getDocument(DocumentId id)
, meninggalkanDocumentId
kelas sebagai mati, tidak ada perilaku, kelas struct-seperti.
sumber
Jawaban:
Apa yang Anda gambarkan dikenal sebagai model domain anemia . Seperti banyak prinsip desain OOP (seperti Law of Demeter dll.), Tidak ada gunanya membungkuk ke belakang hanya untuk memenuhi aturan.
Tidak ada salahnya memiliki sekumpulan nilai, asalkan tidak mengacaukan seluruh lanskap dan tidak bergantung pada objek lain untuk melakukan pembersihan yang bisa mereka lakukan sendiri .
Itu pasti akan menjadi bau kode jika Anda memiliki kelas terpisah hanya untuk memodifikasi properti
Card
- jika bisa diharapkan untuk merawatnya sendiri.Tetapi apakah itu benar-benar pekerjaan
Card
untuk mengetahui yangPlayer
terlihat?Dan mengapa menerapkan
Card.isVisibleTo(Player p)
, tetapi tidakPlayer.isVisibleTo(Card c)
? Atau sebaliknya?Ya, Anda dapat mencoba untuk membuat semacam aturan untuk itu seperti yang Anda lakukan - seperti
Player
menjadi lebih tinggi daripadaCard
(?) - tapi itu tidak mudah ditebak dan saya harus mencari di lebih dari satu tempat untuk menemukan metodenya.Seiring waktu dapat menyebabkan desain kompromi busuk menerapkan
isVisibleTo
pada keduaCard
danPlayer
kelas, yang saya percaya adalah tidak-tidak. Kenapa begitu? Karena saya sudah membayangkan hari yang memalukan ketikaplayer1.isVisibleTo(card1)
akan mengembalikan nilai yang berbeda dari yangcard1.isVisibleTo(player1).
saya kira - ini subjektif - ini harus dibuat mustahil oleh desain .Visibilitas timbal balik antara kartu dan pemain sebaiknya diatur oleh semacam objek konteks - baik itu
Viewport
,Deal
atauGame
.Ini tidak sama dengan memiliki fungsi global. Bagaimanapun, mungkin ada banyak game bersamaan. Perhatikan bahwa kartu yang sama dapat digunakan secara bersamaan di banyak tabel. Haruskah kita membuat banyak
Card
contoh untuk setiap kartu as?Saya mungkin masih menerapkan
isVisibleTo
padaCard
, tapi lulus objek konteks untuk itu dan membuatCard
delegasi query. Program untuk antarmuka untuk menghindari kopling tinggi.Adapun contoh kedua Anda - jika ID dokumen hanya terdiri dari a
BigDecimal
, mengapa membuat kelas wrapper sama sekali?Saya akan mengatakan yang Anda butuhkan adalah a
DocumentRepository.getDocument(BigDecimal documentID);
By the way, saat absen dari Jawa, ada
struct
s di C #.Lihat
http://msdn.microsoft.com/en-us/library/ah19swz4.aspx
http://msdn.microsoft.com/en-us/library/0taef578.aspx
sebagai referensi. Ini adalah bahasa yang sangat berorientasi objek, tetapi tidak ada yang membuat masalah besar dari itu.
sumber
Interface iObj = (Interface)obj;
, perilakuiObj
tidak dipengaruhi olehstruct
atauclass
statusobj
(kecuali bahwa itu akan menjadi salinan kotak pada tugas itu jika itu adalahstruct
)Anda membuat kesalahan umum dengan mengasumsikan bahwa kelas adalah konsep dasar dalam OOP. Kelas hanyalah satu cara yang sangat populer untuk mencapai enkapsulasi. Tapi kita bisa membiarkannya meluncur.
SURGA BAIK NO. Ketika Anda bermain Bridge, apakah Anda meminta tujuh hati ketika tiba waktunya untuk mengubah tangan boneka itu dari rahasia yang hanya diketahui oleh boneka menjadi diketahui oleh semua orang? Tentu saja tidak. Itu sama sekali bukan urusan kartu.
Keduanya mengerikan; jangan lakukan salah satu dari itu. Baik pemain maupun kartu tidak bertanggung jawab untuk menerapkan aturan Bridge!
Saya belum pernah bermain kartu dengan "viewport" sebelumnya, jadi saya tidak tahu apa yang seharusnya dienkapsulasi oleh kelas ini. Saya telah bermain kartu dengan beberapa set kartu, beberapa pemain, meja, dan salinan Hoyle. Salah satu hal yang diwakili Viewport?
Baik!
Tidak; ide dasar dari OOP adalah bahwa objek merangkum keprihatinan mereka . Di sistem Anda, sebuah kartu tidak terlalu peduli. Tidak ada pemain. Ini karena Anda memodelkan dunia secara akurat . Di dunia nyata, propertinya adalah kartu yang relevan dengan permainan yang sangat sederhana. Kami dapat mengganti gambar pada kartu dengan angka dari 1 hingga 52 tanpa banyak mengubah permainan. Kami bisa mengganti empat orang dengan boneka yang berlabel Utara, Selatan, Timur dan Barat tanpa banyak mengubah permainan. Pemain dan kartu adalah hal paling sederhana di dunia permainan kartu. Aturannya adalah apa yang rumit, sehingga kelas yang mewakili aturan adalah tempat kerumitan seharusnya.
Sekarang, jika salah satu pemain Anda adalah AI, maka keadaan internalnya bisa sangat rumit. Tetapi AI itu tidak menentukan apakah ia dapat melihat kartu. Aturan menentukan itu .
Inilah cara saya mendesain sistem Anda.
Pertama, kartu secara mengejutkan rumit jika ada permainan dengan lebih dari satu deck. Anda harus mempertimbangkan pertanyaan: dapatkah pemain membedakan antara dua kartu dengan peringkat yang sama? Jika pemain satu memainkan salah satu dari tujuh hati, dan kemudian beberapa hal terjadi, dan kemudian dua pemain memainkan salah satu dari tujuh hati, dapat pemain tiga menentukan bahwa itu adalah sama tujuh hati? Pertimbangkan ini dengan cermat. Namun terlepas dari kekhawatiran itu, kartu harus sangat sederhana; mereka hanya data.
Selanjutnya, apa sifat pemain? Seorang pemain mengkonsumsi urutan tindakan yang terlihat dan menghasilkan suatu tindakan .
Objek aturan adalah apa yang mengoordinasikan semua ini. Aturan menghasilkan urutan tindakan yang terlihat dan memberi tahu para pemain:
Dan kemudian meminta pemain untuk bertindak.
Dan seterusnya.
Pisahkan mekanisme Anda dari kebijakan Anda . Kebijakan permainan harus diringkas dalam objek kebijakan , bukan dalam kartu . Kartu hanyalah sebuah mekanisme.
sumber
Anda benar bahwa menggabungkan data dan perilaku adalah ide sentral dari OOP, tetapi ada lebih dari itu. Misalnya, enkapsulasi : OOP / pemrograman modular memungkinkan kami untuk memisahkan antarmuka publik dari detail implementasi. Dalam OOP ini berarti bahwa data tidak boleh diakses secara publik, dan hanya boleh digunakan melalui pengakses. Dengan definisi ini, sebuah objek tanpa metode apa pun memang tidak berguna.
Kelas yang tidak menawarkan metode di luar aksesor pada dasarnya adalah struktur yang terlalu rumit. Tapi ini tidak buruk, karena OOP memberi Anda keleluasaan untuk mengubah detail internal, yang mana struct tidak. Misalnya, alih-alih menyimpan nilai dalam bidang anggota, ini bisa dihitung ulang setiap kali. Atau algoritma dukungan diubah, dan dengan itu keadaan yang harus dilacak.
Sementara OOP memiliki beberapa keuntungan yang jelas (terutama dibandingkan pemrograman prosedural biasa), adalah naif untuk berjuang untuk OOP "murni". Beberapa masalah tidak memetakan dengan baik untuk pendekatan berorientasi objek, dan diselesaikan dengan lebih mudah oleh paradigma lain. Ketika menghadapi masalah seperti itu, jangan memaksakan pendekatan yang lebih rendah.
Pertimbangkan menghitung urutan Fibonacci dengan cara yang berorientasi objek . Saya tidak bisa memikirkan cara yang waras untuk melakukan itu; pemrograman terstruktur sederhana menawarkan solusi terbaik untuk masalah ini.
isVisible
Relasi Anda milik kedua kelas, atau bukan keduanya, atau sebenarnya: dengan konteks . Catatan tanpa perilaku adalah tipikal dari pendekatan pemrograman fungsional atau prosedural, yang tampaknya paling cocok untuk masalah Anda. Tidak ada yang salah dengan itudan tidak ada yang salah dengan
Card
tidak memiliki metode di luarrank
dansuit
aksesor.sumber
fibonacci
metode ini pada contohinteger
. Saya ingin menekankan poin Anda bahwa OO adalah tentang enkapsulasi, bahkan di tempat-tempat yang tampaknya kecil. Biarkan bilangan bulat mencari tahu bagaimana melakukan pekerjaan itu. Nanti Anda bisa meningkatkan implementasi, menambahkan caching untuk meningkatkan kinerja. Tidak seperti fungsi, metode mengikuti data sehingga semua penelepon mendapat manfaat dari implementasi yang ditingkatkan. Mungkin bilangan bulat presisi sewenang-wenang ditambahkan kemudian, mereka dapat diperlakukan secara transparan seperti bilangan bulat normal dan dapat memilikifibonacci
metode tweak kinerja sendiri .Fibonacci
merupakan subkelas dari kelas abstrakSequence
, urutan digunakan oleh set angka apa pun dan bertanggung jawab untuk menyimpan benih, status, caching, dan iterator.Ini adalah pertanyaan yang sulit karena didasarkan pada beberapa alasan yang salah:
Saya tidak akan terlalu banyak membahas # 1-3, karena masing-masing dapat memunculkan jawaban mereka sendiri, dan itu mengundang banyak diskusi berdasarkan pendapat. Tapi saya menemukan ide "OOP adalah tentang menggabungkan data dan perilaku" menjadi sangat mengganggu. Tidak hanya mengarah ke # 4, itu juga mengarah pada gagasan bahwa semuanya harus menjadi metode.
Ada perbedaan antara operasi yang menentukan tipe, dan cara Anda bisa menggunakan tipe itu. Mampu mengambil
i
elemen th sangat penting untuk konsep array, tetapi menyortir hanyalah salah satu dari banyak hal yang bisa saya pilih untuk dilakukan dengan satu array. Penyortiran tidak perlu menjadi metode seperti "buat array baru yang hanya berisi elemen genap".OOP adalah tentang menggunakan objek. Objek hanyalah salah satu cara untuk mencapai abstraksi . Abstraksi adalah cara untuk menghindari kopling yang tidak perlu dalam kode Anda, bukan tujuan semuanya. Jika pengertian kartu hanya ditentukan oleh nilai paket dan peringkatnya, tidak apa-apa menerapkannya sebagai tuple atau catatan sederhana. Tidak ada perincian yang tidak penting yang dapat menjadi bagian ketergantungan kode ini. Terkadang Anda tidak punya sesuatu untuk disembunyikan.
Anda tidak akan membuat
isVisible
metodeCard
tipe karena terlihat mungkin tidak penting untuk gagasan Anda tentang kartu (kecuali jika Anda memiliki kartu yang sangat khusus yang dapat mengubah tembus atau buram ...). Haruskah ini menjadi metodePlayer
jenisnya? Yah, itu mungkin juga bukan kualitas pemain yang menentukan. Haruskah itu menjadi bagian dari beberapaViewport
jenis? Sekali lagi itu tergantung pada apa yang Anda mendefinisikan viewport menjadi dan apakah gagasan memeriksa visibilitas kartu merupakan bagian integral dari mendefinisikan viewport.Sangat mungkin
isVisible
hanya fungsi bebas.sumber
Tidak, mereka tidak. Objek Data Biasa-Lama adalah pola yang benar-benar valid, dan saya harapkan mereka dalam program apa pun yang berhubungan dengan data yang perlu dipertahankan atau dikomunikasikan di antara area yang berbeda dari program Anda.
Sementara layer data Anda mungkin menggulung
Player
kelas penuh ketika membaca dariPlayers
tabel, itu bisa saja hanya pustaka data umum yang mengembalikan POD dengan bidang dari tabel, yang dilewatkan ke area lain dari program Anda yang mengonversi pemain POD kePlayer
kelas konkret Anda .Penggunaan objek data, baik yang diketik atau tidak, mungkin tidak masuk akal dalam program Anda, tetapi itu tidak membuat mereka anti-pola. Jika mereka masuk akal, gunakan, dan jika tidak, jangan.
sumber
Plain-Old-Data objects are a perfectly valid pattern
Saya tidak mengatakan mereka tidak, saya mengatakan itu salah ketika mereka mengisi seluruh bagian bawah aplikasi.Secara pribadi saya pikir Desain Domain Driven membantu membawa kejelasan untuk masalah ini. Pertanyaan yang saya tanyakan adalah, bagaimana cara saya menggambarkan permainan kartu kepada manusia? Dengan kata lain, apa yang saya modelkan? Jika hal yang saya permodelkan benar-benar termasuk kata "viewport" dan konsep yang sesuai dengan perilakunya, maka saya akan membuat objek viewport dan membuatnya melakukan apa yang seharusnya secara logis.
Namun jika saya tidak memiliki konsep viewport di permainan saya, dan itu adalah sesuatu yang saya pikir saya butuhkan karena kalau tidak kode "merasa salah". Saya berpikir dua kali untuk menambahkannya sebagai model domain saya.
Kata model berarti bahwa Anda sedang membangun representasi sesuatu. Saya memperingatkan untuk tidak memasukkan kelas yang mewakili sesuatu yang abstrak di luar hal yang Anda wakili.
Saya akan mengedit untuk menambahkan bahwa ada kemungkinan bahwa Anda mungkin memerlukan konsep Viewport di bagian lain dari kode Anda, jika Anda perlu berinteraksi dengan tampilan. Tetapi dalam istilah DDD ini akan menjadi masalah infrastruktur dan akan ada di luar model domain.
sumber
Saya biasanya tidak melakukan promosi sendiri, tetapi kenyataannya saya menulis banyak tentang masalah desain OOP di blog saya . Singkatnya beberapa halaman: Anda tidak harus memulai mendesain dengan kelas. Dimulai dengan antarmuka atau API dan kode bentuk dari sana memiliki peluang lebih tinggi untuk memberikan abstraksi yang bermakna, menyesuaikan spesifikasi, dan menghindari kelas-kelas konkret yang membengkak dengan kode yang tidak dapat digunakan kembali.
Bagaimana ini berlaku untuk
Card
-Player
masalah: MembuatViewPort
abstraksi masuk akal jika Anda memikirkanCard
danPlayer
sebagai dua perpustakaan independen (yang menyiratkanPlayer
kadang-kadang digunakan tanpaCard
). Namun, saya cenderung berpikirPlayer
penahananCards
dan harus menyediakanCollection<Card> getVisibleCards ()
aksesor kepada mereka. Kedua solusi ini (ViewPort
dan milik saya) lebih baik daripada menyediakanisVisible
sebagai metodeCard
atauPlayer
, dalam hal menciptakan hubungan kode yang dapat dimengerti.Solusi luar kelas jauh, jauh lebih baik untuk
DocumentId
. Ada sedikit motivasi untuk membuat (pada dasarnya, integer) bergantung pada pustaka database yang kompleks.sumber
Saya tidak yakin pertanyaan yang ada sedang dijawab pada tingkat yang tepat. Saya telah mendesak yang bijak di forum untuk secara aktif memikirkan inti dari pertanyaan di sini.
U Mad memunculkan situasi di mana ia percaya bahwa pemrograman sesuai pemahamannya tentang OOP umumnya akan menghasilkan banyak node daun menjadi pemegang data sementara API tingkat atasnya terdiri dari sebagian besar perilaku.
Saya pikir topiknya sedikit singgung apakah isVisible akan didefinisikan pada Card vs Player; itu hanya contoh yang diilustrasikan, meskipun naif.
Saya telah mendorong yang berpengalaman di sini untuk melihat masalah yang dihadapi. Saya pikir ada pertanyaan bagus yang didesak oleh U Mad. Saya mengerti bahwa Anda akan mendorong aturan dan logika yang bersangkutan ke objeknya sendiri; tapi seperti yang saya mengerti pertanyaannya adalah
Pandangan ku:
Saya pikir Anda mengajukan pertanyaan granularity yang sulit untuk mendapatkan yang benar dalam pemrograman berorientasi objek. Dalam sedikit pengalaman saya, saya tidak akan memasukkan entitas dalam model saya yang tidak menyertakan perilaku apa pun dengan sendirinya. Jika saya harus, saya mungkin telah menggunakan konstruk struct yang dirancang untuk menahan abstraksi seperti tidak seperti kelas yang memiliki ide merangkum data dan perilaku.
sumber
Point
dengangetX()
fungsi. Anda bisa membayangkan mendapatkan salah satu atributnya, tetapi bisa juga membacanya dari disk atau internet. Mendapatkan dan menetapkan adalah perilaku, dan memiliki kelas yang melakukan hal itu sepenuhnya baik-baik saja. Basis data hanya mendapatkan dan mengatur data fwiwSatu sumber kebingungan yang umum dalam OOP berasal dari fakta bahwa banyak objek merangkum dua aspek keadaan: hal-hal yang mereka ketahui, dan hal-hal yang tahu tentang mereka. Diskusi tentang keadaan objek sering mengabaikan aspek yang terakhir, karena dalam kerangka di mana referensi objek bebas, tidak ada cara umum untuk menentukan hal-hal apa yang mungkin diketahui tentang objek apa pun yang rujukannya pernah diekspos ke dunia luar.
Saya akan menyarankan bahwa mungkin akan bermanfaat untuk memiliki
CardEntity
objek yang merangkum aspek-aspek kartu dalam komponen yang terpisah. Satu komponen akan berhubungan dengan tanda pada kartu (Misalnya "Diamond King" atau "Lava Blast; pemain memiliki kesempatan AC-3 untuk menghindar, atau mengambil kerusakan 2D6"). Orang mungkin berhubungan dengan aspek unik dari keadaan seperti posisi (misalnya di dek, atau di tangan Joe, atau di atas meja di depan Larry). Yang ketiga mungkin berhubungan dengan dapat melihatnya (mungkin tidak ada, mungkin satu pemain, atau mungkin banyak pemain). Untuk memastikan bahwa semuanya disimpan dalam sinkronisasi, tempat-tempat di mana kartu mungkin tidak akan dienkapsulasi sebagai bidang sederhana, melainkanCardSpace
objek; untuk memindahkan kartu ke suatu tempat, orang akan memberikan referensi yang tepatCardSpace
obyek; kemudian akan menghapus dirinya sendiri dari ruang lama dan menempatkan dirinya ke ruang baru).Secara enkapsulasi "siapa yang tahu tentang X" secara terpisah dari "apa yang diketahui X" seharusnya membantu menghindari banyak kebingungan. Kadang-kadang diperlukan kehati-hatian untuk menghindari kebocoran memori, terutama dengan banyak asosiasi (misalnya jika kartu baru dapat muncul dan kartu lama hilang, orang harus memastikan bahwa kartu yang harus ditinggalkan tidak dibiarkan melekat terus-menerus pada benda yang berumur panjang. ) tetapi jika keberadaan referensi ke suatu objek akan membentuk bagian yang relevan dari keadaannya, itu sepenuhnya tepat untuk objek itu sendiri untuk secara eksplisit merangkum informasi tersebut (bahkan jika mendelegasikan ke beberapa kelas lain pekerjaan yang sebenarnya mengelola itu).
sumber
Dan bagaimana itu buruk / keliru?
Untuk menggunakan analogi yang mirip dengan contoh kartu Anda, pertimbangkan a
Car
, aDriver
dan Anda perlu menentukan apakahDriver
dapat menggerakkannyaCar
.OK, jadi Anda memutuskan bahwa Anda tidak ingin Anda
Car
tahu apakahDriver
memiliki kunci mobil yang tepat atau tidak, dan untuk beberapa alasan yang tidak diketahui Anda juga memutuskan Anda tidak ingin AndaDriver
tahu tentangCar
kelas (Anda tidak sepenuhnya tahu ini dalam pertanyaan awal Anda juga). Karenanya, Anda memiliki kelas perantara, sesuatu di sepanjang garisUtils
kelas, yang berisi metode dengan aturan bisnis untuk mengembalikanboolean
nilai untuk pertanyaan di atas.Saya pikir ini baik-baik saja. Kelas perantara mungkin hanya perlu memeriksa kunci mobil sekarang, tetapi dapat di refactored untuk mempertimbangkan apakah pengemudi memiliki SIM yang sah, di bawah pengaruh alkohol, atau di masa depan dystopian, periksa biometrik DNA. Dengan enkapsulasi, sebenarnya tidak ada masalah besar yang membuat ketiga kelas ini hidup bersama.
sumber