Seberapa spesifik seharusnya pola Tanggung Jawab Tunggal untuk kelas?

14

Sebagai contoh, misalkan Anda memiliki program permainan konsol, yang memiliki semua jenis metode input / output ke dan dari konsol. Apakah akan pintar untuk menjaga mereka semua dalam satu inputOutputkelas atau istirahat mereka turun ke kelas khusus lebih seperti startMenuIO, inGameIO, playerIO, gameBoardIO, dll sehingga setiap kelas memiliki sekitar 1-5 metode?

Dan pada catatan yang sama, jika lebih baik untuk memecahnya, akankah pintar untuk menempatkan mereka di IOnamespace sehingga membuat memanggil mereka sedikit lebih bertele-tele, misalnya: IO.inGamedll?

Shinzou
sumber
Terkait: Saya belum pernah melihat alasan yang bagus untuk menggabungkan input dan output. Dan ketika saya sengaja memisahkan mereka, kode saya ternyata jauh lebih bersih.
Mooing Duck
1
Dalam bahasa apa Anda menyebutkan kelas di lowerCamelCase?
user253751

Jawaban:

7

Perbarui (rekap)

Karena saya telah menulis jawaban yang agak bertele-tele, inilah yang akhirnya menjadi:

  • Ruang nama itu baik, gunakan kapan pun itu masuk akal
  • Penggunaan inGameIOdan playerIOkelas kemungkinan merupakan pelanggaran SRP. Kemungkinan itu berarti Anda menggabungkan cara Anda menangani IO dengan logika aplikasi.
  • Memiliki beberapa kelas IO generik, yang digunakan (atau terkadang dibagi) oleh kelas handler. Kelas-kelas handler ini kemudian akan menerjemahkan input mentah ke dalam format yang dapat dimengerti oleh logika aplikasi Anda.
  • Hal yang sama berlaku untuk output: ini dapat dilakukan oleh kelas yang cukup umum, tetapi melewati keadaan permainan melalui objek handler / mapper yang menerjemahkan keadaan permainan internal menjadi sesuatu yang bisa ditangani oleh kelas IO generik.

Saya pikir Anda melihat ini dengan cara yang salah. Anda memisahkan fungsi IO dalam komponen aplikasi, sedangkan - untuk lebih masuk akal untuk memiliki kelas IO terpisah berdasarkan sumber, dan "jenis" IO.

Memiliki beberapa KeyboardIOkelas dasar / generik MouseIOuntuk memulai, dan kemudian berdasarkan kapan dan di mana Anda membutuhkannya, memiliki subkelas yang menangani kata IO secara berbeda.
Misalnya, input teks adalah sesuatu yang mungkin ingin Anda tangani secara berbeda untuk kontrol dalam game. Anda akan menemukan diri Anda ingin memetakan kunci tertentu secara berbeda tergantung pada setiap kasus penggunaan, tetapi pemetaan itu bukan bagian dari IO itu sendiri, melainkan bagaimana Anda menangani IO.

Berpegang teguh pada SRP, saya akan memiliki beberapa kelas yang dapat saya gunakan untuk keyboard IO. Bergantung pada situasinya, saya mungkin ingin berinteraksi dengan kelas-kelas ini secara berbeda, tetapi satu-satunya tugas mereka adalah memberi tahu saya apa yang dilakukan pengguna.

Saya kemudian akan menyuntikkan objek-objek ini ke objek handler yang akan memetakan IO mentah ke sesuatu yang logika aplikasi saya dapat bekerja dengan (misalnya: pengguna menekan "w" , handler memetakan itu ke MOVE_FORWARD).

Penangan ini, pada gilirannya digunakan untuk membuat karakter bergerak, dan menggambar layar sesuai. Penyederhanaan yang terlalu berlebihan, tetapi intinya adalah struktur seperti ini:

[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
   ||
   ==> [ Controls.Keyboard.InGameMapper ]

[ Game.Engine ] <- Controls.Keyboard.InGameMapper
                <- IO.Screen
                <- ... all sorts of stuff here
    InGameMapper.move() //returns MOVE_FORWARD or something
      ||
      ==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
          2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
          3. IO.Screen.draw();//generate actual output

Apa yang kita miliki sekarang adalah kelas yang bertanggung jawab atas IO keyboard dalam bentuk mentahnya. Kelas lain yang menerjemahkan data ini menjadi sesuatu yang benar-benar masuk akal oleh mesin permainan, data ini kemudian digunakan untuk memperbarui status semua komponen yang terlibat, dan akhirnya, kelas yang terpisah akan menangani output ke layar.

Setiap kelas memiliki satu pekerjaan: menangani input keyboard dilakukan oleh kelas yang tidak tahu / peduli / harus tahu apa arti input yang diprosesnya. Yang perlu dilakukan adalah mengetahui cara mendapatkan input (buffered, unbuffered, ...).

Pawang menerjemahkan ini ke dalam representasi internal untuk sisa aplikasi untuk memahami info ini.

Mesin permainan mengambil data yang telah diterjemahkan, dan menggunakannya untuk memberi tahu semua komponen yang relevan bahwa sesuatu sedang terjadi. Masing-masing komponen melakukan hanya satu hal, apakah itu pemeriksaan tabrakan, atau perubahan karakter animasi, tidak masalah, itu tergantung pada masing-masing objek.

Objek-objek ini kemudian menyampaikan keadaannya kembali, dan data ini diteruskan ke Game.Screen, yang pada dasarnya adalah penangan IO terbalik. Ini memetakan representasi internal ke sesuatu yang IO.Screendapat digunakan komponen untuk menghasilkan output yang sebenarnya.

Elias Van Ootegem
sumber
Karena ini adalah aplikasi konsol, tidak ada mouse, dan mencetak pesan atau papan terkait erat dengan input. Dalam contoh Anda, apakah IOdan gameruang nama atau kelas dengan subclass?
shinzou
@kuhaku: Itu ruang nama. Inti dari apa yang saya katakan adalah bahwa, jika Anda memilih untuk membuat subclass berdasarkan pada bagian mana dari aplikasi yang Anda gunakan, Anda secara efektif menggabungkan fungsionalitas IO dasar dengan logika aplikasi Anda. Anda akan berakhir dengan kelas-kelas yang bertanggung jawab atas IO dalam fungsi aplikasi . Bagi saya, itu terdengar seperti pelanggaran terhadap SRP. Adapun kelas nama vs namespace: Saya cenderung lebih suka ruang nama 95% dari waktu
Elias Van Ootegem
Saya telah memperbarui jawaban saya, untuk merangkum jawaban saya
Elias Van Ootegem
Ya itu sebenarnya masalah lain yang saya alami (menggabungkan IO dengan logika), jadi Anda benar-benar merekomendasikan untuk memisahkan input dari output?
shinzou
@kuhaku: Itu benar-benar tergantung pada apa yang Anda lakukan, dan seberapa kompleks hal input / output. Jika penangan (menerjemahkan keadaan permainan vs input / output mentah) terlalu banyak berbeda, maka Anda mungkin ingin memisahkan kelas input dan output, jika tidak, satu kelas IO baik
Elias Van Ootegem
23

Prinsip tanggung jawab tunggal bisa sulit dipahami. Apa yang menurut saya bermanfaat adalah menganggapnya seperti bagaimana Anda menulis kalimat. Anda tidak mencoba menjejalkan banyak ide menjadi satu kalimat. Setiap kalimat harus menyatakan satu ide dengan jelas dan menunda detailnya. Misalnya, jika Anda ingin mendefinisikan mobil, Anda akan mengatakan:

Kendaraan jalan, biasanya dengan empat roda, ditenagai oleh mesin pembakaran internal.

Maka Anda akan mendefinisikan hal-hal seperti "kendaraan", "jalan", "roda", dll. Secara terpisah. Anda tidak akan mencoba mengatakan:

Sebuah kendaraan untuk mengangkut orang di jalan raya, rute, atau jalan di darat antara dua tempat yang telah diaspal atau diperbaiki untuk memungkinkan perjalanan yang memiliki empat objek melingkar yang berputar pada poros yang diperbaiki di bawah kendaraan dan didukung oleh mesin yang menghasilkan motif tenaga dengan membakar bensin, minyak, atau bahan bakar lainnya dengan udara.

Demikian juga, Anda harus mencoba membuat kelas, metode, dll., Nyatakan konsep sentral sesederhana mungkin dan tunda detailnya ke metode dan kelas lain. Sama seperti dengan menulis kalimat, tidak ada aturan keras tentang seberapa besar seharusnya.

Vaughn Cato
sumber
Jadi, seperti mendefinisikan mobil dengan beberapa kata, bukan banyak, lalu mendefinisikan kelas spesifik kecil tetapi serupa tidak masalah?
shinzou
2
@kuhaku: Kecil itu bagus. Spesifik itu baik. Serupa itu baik-baik saja selama Anda tetap KERING. Anda mencoba membuat desain Anda mudah diubah. Menjaga hal-hal kecil dan spesifik membantu Anda tahu di mana harus melakukan perubahan. Menjaga KERING membantu menghindari keharusan mengubah banyak tempat.
Vaughn Cato
6
Kalimat kedua Anda terlihat seperti "Sial, guru bilang ini harus 4 halaman ... 'menghasilkan kekuatan motif' itu !!"
corsiKa
2

Saya akan mengatakan bahwa cara terbaik untuk pergi adalah menjaga mereka di kelas yang terpisah. Kelas kecil tidak buruk, bahkan sebagian besar waktu itu adalah ide bagus.

Mengenai kasus spesifik Anda, saya pikir memiliki pemisahan dapat membantu Anda mengubah logika dari salah satu penangan tertentu tanpa mempengaruhi yang lain dan, jika perlu, akan lebih mudah bagi Anda untuk menambahkan metode input / output baru jika itu yang terjadi.

Zalomon
sumber
0

Kepala Satu Tanggung Jawab menyatakan bahwa suatu kelas hanya boleh memiliki satu alasan untuk berubah. Jika kelas Anda memiliki banyak alasan untuk berubah, maka Anda dapat membaginya menjadi kelas lain dan memanfaatkan komposisi untuk menghilangkan masalah ini.

Untuk menjawab pertanyaan Anda, saya harus bertanya kepada Anda: Apakah kelas Anda hanya memiliki satu alasan untuk berubah? Jika tidak, maka jangan takut untuk terus menambahkan kelas khusus sampai masing-masing hanya memiliki satu alasan untuk berubah.

Greg Burghardt
sumber