Dalam seri posting blog ini , Eric Lippert menjelaskan masalah dalam desain berorientasi objek menggunakan penyihir dan prajurit sebagai contoh, di mana:
abstract class Weapon { }
sealed class Staff : Weapon { }
sealed class Sword : Weapon { }
abstract class Player
{
public Weapon Weapon { get; set; }
}
sealed class Wizard : Player { }
sealed class Warrior : Player { }
dan kemudian menambahkan beberapa aturan:
- Seorang prajurit hanya bisa menggunakan pedang.
- Seorang penyihir hanya bisa menggunakan staf.
Dia kemudian melanjutkan untuk mendemonstrasikan masalah yang Anda hadapi jika Anda mencoba untuk menegakkan aturan-aturan ini menggunakan sistem tipe C # (misalnya membuat Wizard
kelas bertanggung jawab untuk memastikan bahwa seorang penyihir hanya dapat menggunakan staf). Anda melanggar Prinsip Substitusi Liskov, mengambil risiko pengecualian waktu berjalan atau berakhir dengan kode yang sulit diperpanjang.
Solusi yang ia temukan adalah tidak ada validasi yang dilakukan oleh kelas Player. Ini hanya digunakan untuk melacak status. Kemudian, alih-alih memberikan senjata kepada pemain dengan:
player.Weapon = new Sword();
negara diubah oleh Command
s dan menurut Rule
s:
... kami membuat
Command
objek bernamaWield
yang membutuhkan dua objek kondisi permainan, aPlayer
dan aWeapon
. Ketika pengguna mengeluarkan perintah ke sistem "wizard ini harus menggunakan pedang itu", maka perintah itu dievaluasi dalam konteks seperangkatRule
s, yang menghasilkan urutanEffect
s. Kami memiliki satuRule
yang mengatakan bahwa ketika seorang pemain mencoba untuk menggunakan senjata, efeknya adalah bahwa senjata yang ada, jika ada, dijatuhkan dan senjata baru menjadi senjata pemain. Kami memiliki aturan lain yang memperkuat aturan pertama, yang mengatakan bahwa efek aturan pertama tidak berlaku ketika seorang penyihir mencoba memegang pedang.
Saya menyukai ide ini pada prinsipnya, tetapi memiliki keprihatinan tentang bagaimana itu dapat digunakan dalam praktik.
Sepertinya tidak ada yang menghalangi pengembang untuk melewati Commands
dan Rule
s dengan hanya mengatur Weapon
pada a Player
. The Weapon
properti perlu diakses oleh Wield
perintah, sehingga tidak dapat dibuat private set
.
Jadi, apa yang dilakukannya mencegah pengembang dari melakukan hal ini? Apakah mereka hanya perlu ingat untuk tidak melakukannya?
Jawaban:
Seluruh argumen yang mengarah pada serangkaian posting blog ada di Bagian Lima :
Senjata, karakter, monster, dan objek game lainnya tidak bertanggung jawab untuk memeriksa apa yang dapat atau tidak bisa mereka lakukan. Sistem aturan bertanggung jawab untuk itu. The
Command
objek tidak melakukan apa-apa dengan objek permainan baik. Itu hanya merupakan upaya untuk melakukan sesuatu dengan mereka. Sistem aturan kemudian memeriksa apakah perintah itu mungkin, dan ketika itu sistem aturan menjalankan perintah dengan memanggil metode yang sesuai pada objek permainan.Jika pengembang ingin membuat sistem aturan kedua yang melakukan hal-hal dengan karakter dan senjata yang tidak diizinkan oleh sistem aturan pertama, mereka bisa melakukannya karena di C # Anda tidak dapat (tanpa peretasan refleksi jahat) mencari tahu dari mana panggilan metode datang dari.
Solusi yang mungkin berhasil dalam beberapa situasi adalah menempatkan objek game (atau antarmuka mereka) dalam satu rakitan dengan mesin aturan dan tandai metode mutator apa pun sebagai
internal
. Sistem apa pun yang membutuhkan akses hanya baca ke objek game akan berada dalam majelis berbeda, yang berarti mereka hanya akan dapat mengaksespublic
metode. Ini masih menyisakan celah objek permainan memanggil metode internal masing-masing. Tetapi melakukan hal itu akan menjadi bau kode yang jelas, karena Anda setuju bahwa kelas objek game seharusnya adalah negara-bodoh.sumber
Masalah yang jelas dari kode asli adalah melakukan Pemodelan Data, bukan Pemodelan Objek . Harap dicatat bahwa sama sekali tidak disebutkan persyaratan bisnis aktual dalam artikel tertaut!
Saya akan mulai dengan mencoba mendapatkan persyaratan fungsional yang sebenarnya. Misalnya: "Pemain mana saja dapat menyerang pemain lain, ...". Sini:
"Pemain dapat menggunakan senjata yang digunakan dalam serangan, Penyihir dapat menggunakan Staf, Warriors a Sword":
"Setiap senjata memberikan Damage pada musuh yang diserang". Oke, sekarang kita harus memiliki antarmuka umum untuk Senjata:
Dan seterusnya ... Mengapa tidak ada
Wield()
di dalamnyaPlayer
? Karena tidak ada persyaratan bahwa Pemain dapat menggunakan Senjata apa pun.Saya dapat membayangkan, bahwa akan ada persyaratan yang mengatakan: "Siapa pun
Player
dapat mencoba menggunakan apa punWeapon
." Namun ini akan menjadi hal yang sangat berbeda. Saya akan memodelkannya seperti ini:Ringkasan: Modelkan persyaratan, dan hanya persyaratan. Jangan melakukan pemodelan data, itu bukan pemodelan oo.
sumber
Salah satu cara adalah dengan meneruskan
Wield
perintah kePlayer
. Pemain kemudian menjalankanWield
perintah, yang memeriksa aturan yang sesuai dan mengembalikanWeapon
, yangPlayer
kemudian menetapkan bidang Senjata sendiri. Dengan cara ini, bidang Senjata dapat memiliki setter pribadi dan hanya dapat diselesaikan melalui pemberianWield
perintah kepada pemain.sumber
Tidak ada yang mencegah pengembang melakukan itu. Sebenarnya Eric Lippert mencoba banyak teknik berbeda tetapi mereka semua memiliki kelemahan. Itulah inti dari seri itu yang menghentikan pengembang dari melakukan itu tidak mudah dan semua yang ia coba memiliki kelemahan. Akhirnya ia memutuskan bahwa menggunakan
Command
objek dengan aturan adalah jalan yang harus ditempuh.Dengan aturan, Anda dapat mengatur
Weapon
properti aWizard
menjadiSword
tetapi ketika Anda memintaWizard
untuk menggunakan senjata (Pedang) dan menyerang itu tidak akan memiliki efek dan karenanya tidak akan mengubah keadaan apa pun. Seperti yang dia katakan di bawah ini:Dengan kata lain, kita tidak dapat menegakkan aturan seperti itu melalui
type
hubungan yang dia coba berbagai cara tetapi entah tidak suka atau tidak berhasil. Jadi satu-satunya hal yang dia katakan bisa kita lakukan adalah melakukan sesuatu tentang hal itu saat runtime. Melempar pengecualian itu tidak baik karena ia tidak menganggapnya sebagai pengecualian.Dia akhirnya memilih untuk pergi dengan solusi di atas. Solusi ini pada dasarnya mengatakan Anda dapat mengatur senjata apa pun tetapi ketika Anda menghasilkannya, jika bukan senjata yang tepat, itu pada dasarnya tidak berguna. Tapi tidak terkecuali akan terlempar.
Saya pikir ini solusi yang bagus. Meskipun dalam beberapa kasus saya akan pergi dengan pola coba-atur juga.
sumber
This solution basically says you can set any weapon but when you yield it, if not the right weapon, it would be essentially useless.
Saya gagal menemukannya dalam seri itu, bisakah Anda menunjukkan kepada saya di mana solusi ini diusulkan?that the existing weapon, if there is one, is dropped and the new weapon becomes the player’s weapon
. Sedangkan aturan kedua yaituthat strengthens the first rule, that says that the first rule’s effects do not apply when a wizard tries to wield a sword.
Jadi saya pikir ada aturan yang memeriksa apakah senjata itu adalah pedang, jadi tidak bisa dikuasai wizzard, jadi tidak disetel. Alih-alih terdengar trombone yang sedih.Wield
sini. Saya pikir itu adalah nama yang agak menyesatkan untuk perintah. Sesuatu sepertiChangeWeapon
itu akan lebih akurat. Saya kira Anda bisa memiliki model yang berbeda di mana Anda dapat mengatur senjata apa pun tetapi ketika Anda menghasilkannya, jika bukan senjata yang tepat, itu pada dasarnya tidak berguna . Kedengarannya menarik, tapi menurut saya itu yang dijelaskan Eric Lippert.Solusi pertama yang dibuang penulis adalah merepresentasikan aturan oleh sistem tipe. Sistem tipe dievaluasi pada waktu kompilasi. Jika Anda melepaskan aturan dari sistem tipe, mereka tidak diperiksa lagi oleh kompiler, jadi tidak ada yang mencegah pengembang membuat kesalahan sendiri.
Tetapi masalah ini dihadapi oleh setiap bagian dari logika / pemodelan yang tidak diperiksa oleh kompiler dan jawaban umum untuk ini adalah (unit) pengujian. Oleh karena itu, solusi yang diusulkan penulis membutuhkan uji coba yang kuat untuk menghindari kesalahan oleh pengembang. Untuk menggarisbawahi titik ini membutuhkan uji coba yang kuat untuk kesalahan yang hanya terdeteksi saat runtime, lihat artikel ini oleh Bruce Eckel, yang membuat argumen bahwa Anda perlu menukar pengetikan yang kuat untuk pengujian yang lebih kuat dalam bahasa dinamis.
Sebagai kesimpulan, satu-satunya hal yang dapat mencegah pengembang membuat kesalahan adalah memiliki serangkaian (unit) tes yang memeriksa bahwa semua aturan dipatuhi.
sumber
Saya mungkin telah melewatkan kehalusan di sini, tapi saya tidak yakin masalahnya dengan sistem tipe. Mungkin dengan konvensi dalam C #.
Misalnya, Anda bisa membuat ini sepenuhnya aman dengan membuat
Weapon
setter terlindungiPlayer
. Kemudian tambahkansetSword(Sword)
dansetStaff(Staff)
keWarrior
danWizard
masing-masing yang memanggil setter yang dilindungi.Dengan begitu,
Player
/Weapon
hubungan yang statis diperiksa dan kode yang tidak peduli hanya dapat menggunakanPlayer
untuk mendapatkanWeapon
.sumber
Weapon
kePlayer
. Tetapi tidak ada sistem tipe di mana Anda tidak tahu jenis beton pada waktu kompilasi yang dapat bertindak pada jenis beton tersebut pada waktu kompilasi. Menurut definisi. Skema ini berarti bahwa hanya kasus itulah yang perlu ditangani saat runtime, Karena itu sebenarnya lebih baik daripada skema Eric.Pertanyaan ini secara efektif sama dengan topik perang suci yang disebut "di mana harus meletakkan validasi " (kemungkinan besar juga mencatat ddd).
Jadi, sebelum menjawab pertanyaan ini, seseorang harus bertanya pada diri sendiri: apa sifat aturan yang ingin Anda ikuti? Apakah mereka diukir di atas batu dan mendefinisikan entitas? Apakah melanggar aturan-aturan itu mengakibatkan entitas berhenti menjadi seperti apa adanya? Jika ya, bersama dengan menjaga aturan-aturan ini dalam validasi perintah , taruh juga di entitas. Jadi, jika pengembang lupa untuk memvalidasi perintah, entitas Anda tidak akan berada dalam keadaan tidak valid.
Jika tidak - yah, itu secara inheren menyiratkan bahwa aturan ini spesifik untuk perintah dan tidak boleh berada di entitas domain. Jadi, melanggar aturan ini menghasilkan tindakan yang seharusnya tidak boleh terjadi, tetapi tidak dalam keadaan model yang tidak valid.
sumber