Objek perilaku nol di OOP - dilema desain saya

94

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 Cardkelas ini perlu menentukan visibilitas ke pemain.

Salah satunya adalah dengan memiliki boolean isVisible(Player p)di Cardkelas.

Lain adalah memiliki boolean isVisible(Card c)di Playerkelas.

Saya tidak menyukai pendekatan pertama khususnya karena memberikan pengetahuan tentang Playerkelas tingkat yang lebih tinggi ke kelas tingkat yang lebih rendah Card.

Sebagai gantinya saya memilih opsi ketiga di mana kita memiliki Viewportkelas yang, diberikan Playerdan daftar kartu menentukan kartu mana yang terlihat.

Namun pendekatan ini merampas baik Carddan Playerkelas dari fungsi anggota yang mungkin. Setelah Anda melakukan ini untuk hal-hal lain selain visibilitas kartu, Anda dibiarkan dengan Carddan Playerkelas - kelas yang berisi data murni karena semua fungsi diimplementasikan di kelas-kelas lain, yang sebagian besar kelas tanpa data, hanya metode, seperti di Viewportatas.

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 DocumentIdyang tidak berubah, hanya memiliki satu BigDecimal idanggota dan pengambil untuk anggota ini. Sekarang Anda perlu memiliki metode di suatu tempat, yang memberikan DocumentIdpengembalian Documentuntuk id ini dari database.

Apakah kamu:

  • Tambahkan Document getDocument(SqlSession)metode ke DocumentIdkelas, 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), meninggalkan DocumentIdkelas sebagai mati, tidak ada perilaku, kelas struct-seperti.
RokL
sumber
21
Beberapa tempat Anda di sini benar-benar salah, yang akan membuat menjawab pertanyaan mendasar sangat sulit. Pertahankan pertanyaan Anda sesingkat dan bebas pendapat yang Anda bisa dan Anda akan mendapatkan jawaban yang lebih baik.
pdr
31
"Ini jelas bertentangan dengan gagasan utama OOP" - tidak, ini bukan, tetapi merupakan kesalahan yang umum.
Doc Brown
5
Saya kira masalahnya terletak pada kenyataan bahwa ada sekolah yang berbeda untuk "Orientasi Objek" di masa lalu - cara yang semula dimaksudkan oleh orang-orang seperti Alan Kay (lihat geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/) ... ), dan caranya diajarkan dalam konteks OOA / OOD oleh orang-orang dari Rasional ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Doc Brown
21
Ini adalah pertanyaan yang sangat bagus, dan diposting dengan baik - bertentangan dengan beberapa komentar lain, saya katakan. Ini menunjukkan dengan jelas betapa naif atau tidak lengkapnya sebagian besar saran tentang bagaimana menyusun program - dan betapa sulitnya melakukannya, dan betapa tidak terjangkaunya desain yang tepat dalam banyak situasi, tidak peduli berapa banyak orang berusaha melakukannya dengan benar. Dan meskipun jawaban yang jelas untuk pertanyaan spesifik adalah multi-metode, masalah mendasar dari desain tetap ada.
Thiago Silva
5
Siapa bilang kelas tanpa cela adalah anti-pola?
James Anderson

Jawaban:

42

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 Carduntuk mengetahui yang Playerterlihat?

Dan mengapa menerapkan Card.isVisibleTo(Player p), tetapi tidak Player.isVisibleTo(Card c)? Atau sebaliknya?

Ya, Anda dapat mencoba untuk membuat semacam aturan untuk itu seperti yang Anda lakukan - seperti Playermenjadi lebih tinggi daripada Card(?) - 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 isVisibleTopada kedua Card dan Playerkelas, yang saya percaya adalah tidak-tidak. Kenapa begitu? Karena saya sudah membayangkan hari yang memalukan ketika player1.isVisibleTo(card1)akan mengembalikan nilai yang berbeda dari yang card1.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, Dealatau Game.

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 Cardcontoh untuk setiap kartu as?

Saya mungkin masih menerapkan isVisibleTopada Card, tapi lulus objek konteks untuk itu dan membuat Carddelegasi 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 structs di C #.

Lihat

sebagai referensi. Ini adalah bahasa yang sangat berorientasi objek, tetapi tidak ada yang membuat masalah besar dari itu.

Konrad Morawski
sumber
1
Hanya sebuah catatan tentang struktur dalam C #: Mereka bukan struct yang khas, seperti yang Anda kenal di C. Bahkan, mereka juga mendukung OOP dengan pewarisan, enkapsulasi dan polimorfisme. Selain beberapa kekhasan, perbedaan utama adalah bagaimana runtime menangani instance ketika dilewatkan ke objek lain: struktur adalah tipe nilai dan kelas adalah tipe referensi!
Aschratt
3
@ Aschratt: Struktur tidak mendukung warisan. Struktur dapat mengimplementasikan antarmuka, tetapi struktur yang mengimplementasikan antarmuka berperilaku berbeda dari objek kelas yang melakukan hal yang sama. Meskipun mungkin untuk membuat struct berperilaku agak seperti objek, kasus penggunaan terbaik untuk struct ketika seseorang menginginkan sesuatu yang berperilaku seperti struct C, dan hal-hal yang dirangkum adalah primitif atau tipe kelas yang tidak dapat diubah.
supercat
1
Memberi +1 untuk alasan "tidak sama dengan memiliki fungsi global." Ini belum banyak ditangani oleh orang lain. (Walaupun jika Anda memiliki beberapa deck, fungsi global masih akan mengembalikan nilai yang berbeda untuk instance berbeda dari kartu yang sama).
alexis
@supercat Ini layak untuk pertanyaan terpisah atau sesi obrolan, tapi saya saat ini tidak tertarik pada :-( Anda mengatakan (dalam C #) "struktur yang mengimplementasikan antarmuka berperilaku berbeda dari objek kelas yang melakukan hal yang sama". Saya setuju bahwa ada perbedaan perilaku lain yang perlu dipertimbangkan, tetapi AFAIK dalam mengikuti kode Interface iObj = (Interface)obj;, perilaku iObjtidak dipengaruhi oleh structatau classstatus obj(kecuali bahwa itu akan menjadi salinan kotak pada tugas itu jika itu adalah struct)
Mark Hurd
150

Ide dasar di balik OOP adalah bahwa data dan perilaku (berdasarkan data itu) tidak dapat dipisahkan dan mereka digabungkan oleh gagasan objek kelas.

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.

Misalkan Anda memiliki API permainan kartu umum. Anda memiliki Kartu kelas. Sekarang kelas Kartu ini perlu menentukan visibilitas ke pemain.

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.

Salah satu caranya adalah memiliki boolean isVisible (Player p) di kelas Card. Lain adalah memiliki boolean isVisible (Kartu c) pada kelas Player.

Keduanya mengerikan; jangan lakukan salah satu dari itu. Baik pemain maupun kartu tidak bertanggung jawab untuk menerapkan aturan Bridge!

Alih-alih, saya memilih opsi ketiga di mana kami memiliki kelas Viewport yang, mengingat Player dan daftar kartu menentukan kartu mana yang terlihat.

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?

Namun pendekatan ini merampas kelas Kartu dan Pemain dari fungsi anggota yang mungkin.

Baik!

Setelah Anda melakukan ini untuk hal-hal lain selain visibilitas kartu, Anda dibiarkan dengan kartu dan kelas Player yang berisi data murni karena semua fungsi diimplementasikan di kelas lain, yang sebagian besar kelas tanpa data, hanya metode, seperti Viewport di atas. Ini jelas bertentangan dengan gagasan utama OOP.

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:

  • Pemain satu, sepuluh hati telah diserahkan kepada Anda oleh pemain tiga.
  • Pemain dua, satu kartu telah diserahkan kepada pemain satu demi satu pemain tiga.

Dan kemudian meminta pemain untuk bertindak.

  • Pemain satu, apa yang ingin Anda lakukan?
  • Pemain satu mengatakan: treble the fromp.
  • Pemain satu, itu adalah tindakan ilegal karena fromp trebled menghasilkan gambit yang tidak dapat dipertahankan.
  • Pemain satu, apa yang ingin Anda lakukan?
  • Pemain satu mengatakan: buang ratu sekop.
  • Pemain dua, pemain satu telah membuang ratu sekop.

Dan seterusnya.

Pisahkan mekanisme Anda dari kebijakan Anda . Kebijakan permainan harus diringkas dalam objek kebijakan , bukan dalam kartu . Kartu hanyalah sebuah mekanisme.

Eric Lippert
sumber
41
@gnat: Pendapat yang berbeda dari Alan Kay adalah "Sebenarnya saya membuat istilah" berorientasi objek ", dan saya dapat memberitahu Anda bahwa saya tidak memiliki C ++ dalam pikiran." Ada bahasa OO tanpa kelas; JavaScript muncul di pikiran.
Eric Lippert
19
@gnat: Saya setuju bahwa JS seperti yang ada saat ini bukan contoh yang bagus dari bahasa OOP, tetapi itu menunjukkan bahwa orang dapat dengan mudah membangun bahasa OO tanpa kelas. Saya setuju bahwa unit dasar OO-ness di Eiffel dan C ++ keduanya adalah kelas; di mana saya tidak setuju adalah gagasan bahwa kelas adalah sine qua non dari OO. The sine qua non dari OO adalah benda-benda yang merangkum perilaku dan berkomunikasi satu sama lain melalui antarmuka publik didefinisikan dengan baik.
Eric Lippert
16
Saya setuju dengan @EricLippert, kelas tidak mendasar untuk OO, juga bukan warisan, apa pun yang dikatakan oleh mainstream. Namun demikian, enkapsulasi data, perilaku, dan tanggung jawab itu tercapai. Ada bahasa berbasis prototipe di luar Javascript yang OO tapi tanpa kelas. Terutama kesalahan untuk fokus pada pewarisan konsep-konsep ini. Yang mengatakan, kelas adalah sarana yang sangat berguna untuk mengatur enkapsulasi perilaku. Bahwa Anda dapat memperlakukan kelas sebagai objek (dan dalam bahasa prototipe, dan sebaliknya) membuat garis menjadi buram.
Schwern
6
Pertimbangkan seperti ini: perilaku apa yang ditunjukkan oleh kartu yang sebenarnya, di dunia nyata, dengan sendirinya? Saya pikir jawabannya adalah "tidak ada." Hal-hal lain berlaku pada kartu. Kartu itu sendiri, di dunia nyata, secara harfiah adalah hanya informasi (4 klub), dengan tidak ada perilaku intrinsik apapun. Cara informasi itu (alias "kartu") digunakan 100% hingga sesuatu / orang lain, alias "aturan" dan "pemain". Kartu yang sama dapat digunakan untuk berbagai jenis permainan yang tak terbatas (yah, mungkin tidak cukup), oleh sejumlah pemain yang berbeda. Kartu hanyalah kartu, dan yang dimilikinya hanyalah properti.
Craig
5
@Montagist: Biarkan saya mengklarifikasi beberapa hal kemudian. Pertimbangkan C. Saya pikir Anda akan setuju bahwa C tidak memiliki kelas. Namun, Anda dapat mengatakan bahwa struct adalah "kelas", Anda bisa membuat bidang tipe pointer fungsi, Anda bisa membuat vtables, Anda bisa membuat metode yang disebut "konstruktor" yang mengatur vtable sehingga beberapa struct "diwariskan” dari satu sama lain, dan seterusnya. Anda bisa meniru warisan berbasis kelas di C. Dan Anda bisa meniru di JS. Tetapi melakukan itu berarti membangun sesuatu di atas bahasa yang belum ada di sana.
Eric Lippert
29

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.

  • isVisibleRelasi 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 itu

    static boolean isVisible(Card c, Player p);
    

    dan tidak ada yang salah dengan Cardtidak memiliki metode di luar rankdan suitaksesor.

amon
sumber
11
@UMAD ya, itu maksud saya persis, dan tidak ada yang salah dengan itu. Gunakan paradigma <del> bahasa </del> yang benar untuk pekerjaan yang sedang dilakukan. (Omong-omong, sebagian besar bahasa selain Smalltalk tidak berorientasi objek murni. Misalnya Java, C # dan C ++ mendukung program imperatif, terstruktur, prosedural, modular, fungsional, dan berorientasi objek. Semua paradigma non-OO tersedia karena suatu alasan : sehingga Anda dapat menggunakannya)
amon
1
Ada cara OO yang masuk akal untuk melakukan Fibonacci, panggil fibonaccimetode ini pada contoh integer. 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 memiliki fibonaccimetode tweak kinerja sendiri .
Schwern
2
@Schwern jika ada yang Fibonaccimerupakan subkelas dari kelas abstrak Sequence, urutan digunakan oleh set angka apa pun dan bertanggung jawab untuk menyimpan benih, status, caching, dan iterator.
George Reith
2
Saya tidak berharap "Fibonacci OOP murni" akan sangat efektif dalam sniping nerd . Tolong hentikan diskusi sirkuler tentang hal itu di komentar ini, meskipun memiliki nilai hiburan tertentu. Sekarang mari kita semua melakukan sesuatu yang konstruktif untuk perubahan!
amon
3
akan konyol untuk menjadikan fibonacci sebagai metode bilangan bulat, supaya Anda bisa mengatakan itu OOP. Ini adalah fungsi dan harus diperlakukan seperti fungsi.
user253751
19

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. (...) Ini jelas bertentangan dengan gagasan utama OOP.

Ini adalah pertanyaan yang sulit karena didasarkan pada beberapa alasan yang salah:

  1. Gagasan bahwa OOP adalah satu-satunya cara yang valid untuk menulis kode.
  2. Gagasan bahwa OOP adalah konsep yang didefinisikan dengan baik. Itu menjadi kata kunci sehingga sulit untuk menemukan dua orang yang dapat menyetujui tentang apa itu OOP.
  3. Gagasan bahwa OOP adalah tentang menggabungkan data dan perilaku.
  4. Gagasan bahwa semuanya adalah / harus menjadi abstraksi.

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 ielemen 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 isVisiblemetode Cardtipe 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 metode Playerjenisnya? Yah, itu mungkin juga bukan kualitas pemain yang menentukan. Haruskah itu menjadi bagian dari beberapa Viewportjenis? 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 isVisiblehanya fungsi bebas.

Doval
sumber
1
Memberi +1 pada akal sehat, bukannya mengoceh tanpa alasan.
sayap kanan
Dari baris yang saya baca, esai yang Anda tautkan terlihat seperti satu bacaan padat yang belum saya miliki dalam beberapa saat.
Arthur Havlicek
@ArthurHavlicek Lebih sulit untuk diikuti jika Anda tidak mengerti bahasa yang digunakan dalam contoh kode, tapi saya merasa cukup menerangi.
Doval
9

Jelas dengan prinsip-prinsip OOP, objek yang hanya data (seperti C struct) dianggap sebagai anti-pola.

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 Playerkelas penuh ketika membaca dari Playerstabel, 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 ke Playerkelas 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.

DougM
sumber
5
Jangan tidak setuju dengan apa pun yang Anda katakan, tetapi ini tidak menjawab pertanyaan sama sekali. Yang mengatakan, saya menyalahkan pertanyaan lebih dari jawabannya.
pdr
2
Tepatnya, kartu dan dokumen hanyalah wadah informasi bahkan di dunia nyata dan "pola" apa pun yang tidak dapat mengatasinya perlu diabaikan.
JeffO
1
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.
RokL
8

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.

RibaldEddie
sumber
Jawaban Lippert di atas adalah contoh yang lebih baik dari konsep ini.
RibaldEddie
5

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- Playermasalah: Membuat ViewPortabstraksi masuk akal jika Anda memikirkan Carddan Playersebagai dua perpustakaan independen (yang menyiratkan Playerkadang-kadang digunakan tanpa Card). Namun, saya cenderung berpikir Playerpenahanan Cardsdan harus menyediakan Collection<Card> getVisibleCards ()aksesor kepada mereka. Kedua solusi ini ( ViewPortdan milik saya) lebih baik daripada menyediakan isVisiblesebagai metode Cardatau Player, 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.

Arthur Havlicek
sumber
Saya suka blog Anda.
RokL
3

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

  1. Apakah boleh memiliki konstruk pemegang data sederhana (kelas / struct; Saya tidak peduli tentang apa yang dimodelkan untuk pertanyaan ini) yang tidak benar-benar menawarkan banyak fungsionalitas?
  2. Jika ya, Apa cara terbaik atau pilihan untuk memodelkan mereka?
  3. Jika tidak, bagaimana kami memasukkan komponen penghitung data ini ke dalam kelas API yang lebih tinggi (termasuk perilaku)

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.

Harsha
sumber
3
Masalahnya adalah bahwa pertanyaan (dan jawaban Anda) adalah tentang bagaimana melakukan sesuatu "secara umum". Faktanya adalah, kita tidak pernah melakukan hal-hal "secara umum". Kami selalu melakukan hal-hal tertentu. Penting untuk memeriksa hal-hal spesifik kita dan mengukurnya terhadap persyaratan kita untuk menentukan apakah hal-hal spesifik kita adalah hal yang benar untuk situasi tersebut.
John Saunders
@ JohnSaunders Saya memahami kebijaksanaan Anda di sini dan setuju sampai batas tertentu tetapi ada juga pendekatan konseptual belaka yang diperlukan sebelum menangani masalah. Bagaimanapun, pertanyaan di sini tidak terbuka seperti yang terlihat. Saya pikir ini adalah pertanyaan OOD yang valid yang dihadapi oleh perancang OO dalam penggunaan awal OOP. Apa yang Anda ambil? Jika konkret membantu, kita dapat membahas membangun contoh pilihan Anda.
Harsha
Saya sudah keluar dari sekolah selama lebih dari 35 tahun sekarang. Di dunia nyata, saya menemukan sangat sedikit nilai dalam "pendekatan konseptual". Saya menemukan pengalaman menjadi guru yang lebih baik daripada Meyers dalam kasus ini.
John Saunders
Saya tidak benar-benar memahami kelas data vs kelas untuk perbedaan perilaku. Jika Anda abstrak objek Anda dengan benar, tidak ada perbedaan. Bayangkan sebuah Pointdengan getX()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 fwiw
Arthur Havlicek
@ArthurHavlicek: Mengetahui apa yang tidak akan dilakukan oleh suatu kelas seringkali sama bermanfaatnya dengan mengetahui apa yang akan dilakukan kelas. Memiliki sesuatu yang menentukan dalam kontraknya bahwa itu akan berperilaku sebagai tidak lebih dari pemegang data abadi yang dapat dibagi, atau tidak lebih dari pemegang data yang tidak dapat dibagi, berguna.
supercat
2

Satu 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 CardEntityobjek 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, melainkan CardSpaceobjek; untuk memindahkan kartu ke suatu tempat, orang akan memberikan referensi yang tepatCardSpaceobyek; 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).

supercat
sumber
0

Namun pendekatan ini merampas kelas Kartu dan Pemain dari fungsi anggota yang mungkin.

Dan bagaimana itu buruk / keliru?

Untuk menggunakan analogi yang mirip dengan contoh kartu Anda, pertimbangkan a Car, a Driverdan Anda perlu menentukan apakah Driverdapat menggerakkannya Car.

OK, jadi Anda memutuskan bahwa Anda tidak ingin Anda Cartahu apakah Drivermemiliki kunci mobil yang tepat atau tidak, dan untuk beberapa alasan yang tidak diketahui Anda juga memutuskan Anda tidak ingin Anda Drivertahu tentang Carkelas (Anda tidak sepenuhnya tahu ini dalam pertanyaan awal Anda juga). Karenanya, Anda memiliki kelas perantara, sesuatu di sepanjang garis Utilskelas, yang berisi metode dengan aturan bisnis untuk mengembalikan booleannilai 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.

hjk
sumber