Saya cukup baru dengan prinsip-prinsip desain SOLID . Saya memahami sebab dan manfaatnya, tetapi saya gagal menerapkannya pada proyek yang lebih kecil yang ingin saya refactor sebagai latihan praktis untuk menggunakan prinsip-prinsip SOLID. Saya tahu tidak perlu mengubah aplikasi yang berfungsi dengan sempurna, tetapi saya tetap ingin mengubahnya jadi saya mendapatkan pengalaman desain untuk proyek masa depan.
Aplikasi memiliki tugas berikut (sebenarnya jauh lebih dari itu tetapi mari kita tetap sederhana): Aplikasi ini harus membaca file XML yang berisi definisi Tabel Tabel / Kolom / Tampilan dll dan membuat file SQL yang dapat digunakan untuk membuat skema database ORACLE.
(Catatan: Tolong jangan membahas mengapa saya membutuhkannya atau mengapa saya tidak menggunakan XSLT dan sebagainya, ada alasannya, tetapi semuanya di luar topik.)
Sebagai permulaan, saya memilih untuk hanya melihat Tabel dan Kendala. Jika Anda mengabaikan kolom, Anda dapat menyatakannya dengan cara berikut:
Kendala adalah bagian dari tabel (atau lebih tepatnya, bagian dari pernyataan CREATE TABLE), dan kendala juga dapat merujuk tabel lain.
Pertama, saya akan menjelaskan seperti apa aplikasi saat ini (tidak menerapkan SOLID):
Saat ini, aplikasi memiliki kelas "Tabel" yang berisi daftar pointer ke Kendala yang dimiliki oleh tabel, dan daftar pointer ke Kendala yang merujuk pada tabel ini. Setiap kali koneksi terbentuk, koneksi mundur juga akan dibuat. Tabel ini memiliki metode createStatement () yang pada gilirannya memanggil fungsi createStatement () dari setiap kendala. Metode Said sendiri akan menggunakan koneksi ke tabel pemilik dan tabel referensi untuk mengambil nama mereka.
Jelas, ini tidak berlaku untuk SOLID sama sekali. Misalnya, ada dependensi melingkar, yang membengkak kode dalam hal metode "tambah" / "hapus" yang diperlukan dan beberapa destruktor objek besar.
Jadi ada beberapa pertanyaan:
- Haruskah saya menyelesaikan dependensi melingkar menggunakan Injeksi Ketergantungan? Jika demikian, saya kira Constraint harus menerima tabel pemilik (dan opsional yang direferensikan) dalam konstruktornya. Tapi bagaimana saya bisa melindas daftar kendala untuk satu tabel?
- Jika kelas Tabel keduanya menyimpan keadaan itu sendiri (misalnya nama tabel, komentar tabel dll) dan tautan ke Kendala, apakah ini satu atau dua "tanggung jawab", memikirkan Prinsip Tanggung Jawab Tunggal?
- Dalam kasus 2. benar, haruskah saya membuat kelas baru di lapisan bisnis logis yang mengelola tautan? Jika demikian, 1. jelas tidak lagi relevan.
- Haruskah metode "createStatement" menjadi bagian dari kelas Tabel / Kendala atau haruskah saya memindahkannya juga? Jika ya, kemana? Satu kelas Manajer per setiap kelas penyimpanan data (mis. Tabel, Kendala, ...)? Atau lebih tepatnya membuat kelas manajer per tautan (mirip dengan 3.)?
Setiap kali saya mencoba menjawab salah satu pertanyaan ini saya menemukan diri saya berputar-putar di suatu tempat.
Masalahnya jelas menjadi jauh lebih kompleks jika Anda memasukkan kolom, indeks dan sebagainya, tetapi jika kalian membantu saya dengan masalah Tabel / Kendala yang sederhana, saya mungkin dapat mengerjakan sisanya sendiri.
sumber
Jawaban:
Anda dapat mulai dari sudut pandang yang berbeda untuk menerapkan "Prinsip Tanggung Jawab Tunggal" di sini. Apa yang Anda tunjukkan kepada kami adalah (kurang lebih) hanya model data aplikasi Anda. SRP di sini berarti: pastikan model data Anda hanya bertanggung jawab untuk menyimpan data - tidak kurang, tidak lebih.
Jadi ketika Anda akan membaca file XML Anda, membuat model data dari itu dan menulis SQL, apa yang tidak boleh Anda lakukan adalah mengimplementasikan apa pun ke dalam
Table
kelas Anda yang XML atau SQL spesifik. Anda ingin aliran data Anda terlihat seperti ini:Jadi satu-satunya tempat di mana kode spesifik XML harus ditempatkan adalah kelas bernama, misalnya
Read_XML
,. Satu-satunya tempat untuk kode khusus SQL adalah kelasWrite_SQL
. Tentu saja, mungkin Anda akan membagi 2 tugas menjadi lebih banyak sub-tugas (dan membagi kelas Anda menjadi beberapa kelas manajer), tetapi "model data" Anda seharusnya tidak bertanggung jawab dari lapisan itu. Jadi jangan tambahkancreateStatement
ke salah satu kelas model data Anda, karena ini memberikan tanggung jawab model data Anda untuk SQL.Saya tidak melihat masalah ketika Anda menjelaskan bahwa sebuah Table bertanggung jawab untuk memegang semua bagiannya, (nama, kolom, komentar, kendala ...), itulah ide di balik model data. Tetapi Anda menggambarkan "Tabel" juga bertanggung jawab untuk manajemen memori beberapa bagiannya. Itu masalah khusus C ++, yang tidak akan Anda hadapi dengan mudah dalam bahasa seperti Java atau C #. Cara C ++ untuk menghilangkan tanggung jawab tersebut adalah menggunakan smart pointer, mendelegasikan kepemilikan ke lapisan yang berbeda (misalnya, perpustakaan pendorong atau ke lapisan pointer "pintar" Anda sendiri). Namun berhati-hatilah, dependensi siklik Anda mungkin "mengganggu" beberapa implementasi smart pointer.
Sesuatu yang lebih tentang SOLID: di sini ada artikel yang bagus
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
menjelaskan SOLID dengan contoh kecil. Mari kita coba menerapkannya pada kasus Anda:
Anda tidak hanya membutuhkan kelas
Read_XML
danWrite_SQL
, tetapi juga kelas ketiga yang mengelola interaksi kedua kelas tersebut. Mari kita sebut aConversionManager
.Menerapkan prinsip DI bisa berarti di sini: ConversionManager tidak harus membuat contoh
Read_XML
danWrite_SQL
dengan sendirinya. Sebaliknya, benda-benda itu dapat disuntikkan melalui konstruktor. Dan konstruktor harus memiliki tanda tangan seperti iniConversionManager(IDataModelReader reader, IDataModelWriter writer)
di mana
IDataModelReader
antarmuka dari manaRead_XML
mewarisi, danIDataModelWriter
sama untukWrite_SQL
. Ini membuatConversionManager
ekstensi terbuka (Anda dengan mudah menyediakan pembaca atau penulis yang berbeda) tanpa harus mengubahnya - jadi kami memiliki contoh untuk prinsip Terbuka / Tertutup. Pikirkan tentang itu apa yang harus Anda ubah ketika Anda ingin mendukung vendor database lain -yaitu, Anda tidak perlu mengubah apa pun dalam model data Anda, cukup sediakan SQL-Writer yang lain.sumber
Nah, Anda harus menerapkan S SOLID dalam hal ini.
Tabel memuat semua batasan yang didefinisikan di atasnya. Batasan memegang semua tabel yang dirujuk. Model polos dan sederhana.
Apa yang Anda yakini, adalah kemampuan untuk melakukan pencarian terbalik, yaitu untuk mencari tahu dengan mana batasan beberapa tabel direferensikan.
Jadi yang Anda inginkan sebenarnya adalah layanan pengindeksan. Itu adalah tugas yang sama sekali berbeda dan karenanya harus dilakukan oleh objek yang berbeda.
Untuk memecahnya ke versi yang sangat sederhana:
Adapun implementasi indeks, ada 3 cara untuk pergi:
getContraintsReferencing
metode bisa benar-benar hanya merangkak seluruhDatabase
untukTable
contoh dan merangkak merekaConstraint
s untuk mendapatkan hasilnya. Bergantung pada seberapa mahal ini dan seberapa sering Anda membutuhkannya, ini bisa menjadi pilihan.Table
danConstraint
instance, ketika mereka berubah. Solusi yang sedikit lebih sederhana adalahIndex
membangun "indeks snapshot" dari keseluruhanDatabase
untuk dikerjakan, yang kemudian akan Anda buang. Itu tentu saja hanya mungkin, jika aplikasi Anda membuat perbedaan besar antara "waktu pemodelan" dan "waktu permintaan". Jika agak mungkin melakukan keduanya pada saat yang sama, maka ini tidak layak.sumber
Obat untuk dependensi melingkar adalah dengan bersumpah bahwa Anda tidak akan pernah membuatnya. Saya menemukan bahwa tes coding pertama adalah pencegah yang kuat.
Lagi pula, dependensi melingkar selalu dapat dipatahkan dengan memperkenalkan kelas dasar abstrak. Ini tipikal untuk representasi grafik. Di sini tabel adalah node dan batasan kunci asing adalah edge. Jadi buat kelas Tabel abstrak dan kelas Kendala abstrak dan mungkin kelas Kolom abstrak. Maka semua implementasi dapat bergantung pada kelas abstrak. Ini mungkin bukan representasi terbaik yang mungkin, tetapi ini merupakan peningkatan dari kelas yang saling digabungkan.
Tetapi, seperti yang Anda duga, solusi terbaik untuk masalah ini mungkin tidak memerlukan pelacakan hubungan objek. Jika Anda hanya ingin menerjemahkan XML ke SQL, maka Anda tidak perlu representasi in-memory dari grafik kendala. Grafik kendala akan bagus jika Anda ingin menjalankan algoritma grafik, tetapi Anda tidak menyebutkannya jadi saya akan menganggap itu bukan keharusan. Anda hanya perlu daftar tabel dan daftar kendala dan pengunjung untuk setiap dialek SQL yang ingin Anda dukung. Hasilkan tabel, kemudian hasilkan kendala eksternal ke tabel. Sampai persyaratan berubah, saya tidak akan punya masalah dengan memasangkan generator SQL ke XML DOM. Simpan besok untuk besok.
sumber