Katakanlah kita memiliki antarmuka berikut -
interface IDatabase {
string ConnectionString{get;set;}
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
Prasyaratnya adalah ConnectionString harus diatur / diinternisasi sebelum salah satu metode dapat dijalankan.
Prasyarat ini dapat agak dicapai dengan melewatkan koneksiString melalui konstruktor jika IDatabase adalah kelas abstrak atau konkret -
abstract class Database {
public string ConnectionString{get;set;}
public Database(string connectionString){ ConnectionString = connectionString;}
public void ExecuteNoQuery(string sql);
public void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
Atau, kita dapat membuat connectionString parameter untuk setiap metode, tetapi terlihat lebih buruk daripada hanya membuat kelas abstrak -
interface IDatabase {
void ExecuteNoQuery(string connectionString, string sql);
void ExecuteNoQuery(string connectionString, string[] sql);
//Various other methods all with the connectionString parameter
}
Pertanyaan -
- Apakah ada cara untuk menentukan prasyarat ini di dalam antarmuka itu sendiri? Ini adalah "kontrak" yang sah jadi saya bertanya-tanya apakah ada fitur bahasa atau pola untuk ini (solusi kelas abstrak lebih merupakan peretasan imo selain kebutuhan membuat dua jenis - antarmuka dan kelas abstrak - setiap kali ini dibutuhkan)
- Ini lebih merupakan keingintahuan teoritis - Apakah prasyarat ini benar-benar jatuh ke dalam definisi prasyarat seperti dalam konteks LSP?
c#
solid
liskov-substitution
Achilles
sumber
sumber
Jawaban:
Iya. Dari .Net 4.0 ke atas, Microsoft menyediakan Kontrak Kode . Ini dapat digunakan untuk mendefinisikan prasyarat dalam formulir
Contract.Requires( ConnectionString != null );
. Namun, untuk membuat ini berfungsi untuk sebuah antarmuka, Anda masih memerlukan kelas pembantuIDatabaseContract
, yang menjadi melekatIDatabase
, dan prasyarat perlu ditentukan untuk setiap metode individu antarmuka Anda di mana ia akan dipegang. Lihat di sini untuk contoh luas untuk antarmuka.Ya , LSP berkaitan dengan bagian sintaksis dan semantik dari suatu kontrak.
sumber
Menghubungkan dan menanyakan adalah dua masalah terpisah. Dengan demikian, mereka harus memiliki dua antarmuka yang terpisah.
Ini memastikan bahwa keduanya
IDatabase
akan terhubung saat digunakan dan membuat klien tidak bergantung pada antarmuka yang tidak diperlukan.sumber
IDatabase
antarmuka mendefinisikan sebuah objek yang mampu membangun koneksi ke database dan kemudian mengeksekusi query sewenang-wenang. Itu adalah objek yang bertindak sebagai batas antara database dan sisa kode. Karena itu, objek ini harus mempertahankan status (seperti transaksi) yang dapat memengaruhi perilaku kueri. Menempatkan mereka di kelas yang sama sangat praktis.Mari kita mundur dan melihat gambaran yang lebih besar di sini.
Apa
IDatabase
tanggung jawabnya?Ini memiliki beberapa operasi berbeda:
Melihat daftar ini, Anda mungkin berpikir, "Bukankah ini melanggar SRP?" Tapi saya rasa tidak. Semua operasi adalah bagian dari konsep kohesif tunggal: mengelola koneksi stateful ke database (sistem eksternal) . Itu membuat koneksi, itu melacak keadaan koneksi saat ini (dalam kaitannya dengan operasi yang dilakukan pada koneksi lain, khususnya), itu menandakan kapan melakukan keadaan koneksi saat ini, dll. Dalam hal ini, ia bertindak sebagai API yang menyembunyikan banyak detail implementasi yang tidak dipedulikan oleh sebagian besar penelepon. Misalnya, apakah menggunakan HTTP, soket, pipa, TCP kustom, HTTPS? Kode panggilan tidak peduli; ia hanya ingin mengirim pesan dan mendapat tanggapan. Ini adalah contoh enkapsulasi yang bagus.
Apakah kita yakin Tidak bisakah kita memisahkan beberapa operasi ini? Mungkin, tetapi tidak ada manfaatnya. Jika Anda mencoba untuk membaginya, Anda masih akan membutuhkan objek pusat yang membuat koneksi tetap terbuka dan / atau mengelola keadaan saat ini. Semua operasi lainnya sangat digabungkan ke negara yang sama, dan jika Anda mencoba untuk memisahkan mereka, mereka hanya akan berakhir mendelegasikan kembali ke objek koneksi. Operasi-operasi ini secara alami dan logis digabungkan ke negara, dan tidak ada cara untuk memisahkannya. Decoupling sangat bagus ketika kita bisa melakukannya, tetapi dalam kasus ini, kita sebenarnya tidak bisa. Setidaknya bukan tanpa protokol stateless yang sangat berbeda untuk berbicara dengan DB, dan itu akan membuat masalah yang sangat penting seperti kepatuhan ACID menjadi lebih sulit. Juga, dalam proses mencoba memisahkan operasi-operasi ini dari koneksi, Anda akan dipaksa untuk mengekspos detail tentang protokol yang tidak dipedulikan penelepon, karena Anda akan memerlukan cara mengirim semacam pesan "sewenang-wenang" ke database.
Perhatikan bahwa fakta yang kita hadapi dengan protokol stateful cukup mengesampingkan alternatif terakhir Anda (melewati string koneksi sebagai parameter).
Apakah kita benar-benar membutuhkan string koneksi untuk diatur?
Iya. Anda tidak dapat membuka koneksi sampai Anda memiliki string koneksi, dan Anda tidak dapat melakukan apa pun dengan protokol sampai Anda membuka koneksi. Jadi tidak ada gunanya memiliki objek koneksi tanpa satu.
Bagaimana kita memecahkan masalah yang membutuhkan string koneksi?
Masalah yang kita coba selesaikan adalah bahwa kita ingin objek berada dalam kondisi yang dapat digunakan setiap saat. Entitas apa yang digunakan untuk mengelola status dalam bahasa OO? Objek , bukan antarmuka. Antarmuka tidak memiliki negara untuk dikelola. Karena masalah yang Anda coba selesaikan adalah masalah manajemen negara, antarmuka tidak benar-benar sesuai di sini. Kelas abstrak jauh lebih alami. Jadi gunakan kelas abstrak dengan konstruktor.
Anda mungkin juga ingin mempertimbangkan untuk benar-benar membuka koneksi selama konstruktor, karena koneksi juga tidak berguna sebelum dibuka. Itu akan membutuhkan
protected Open
metode abstrak karena proses membuka koneksi mungkin spesifik database. Ini juga merupakan ide yang baik untuk membuatConnectionString
properti hanya membaca dalam kasus ini, karena mengubah string koneksi setelah koneksi terbuka tidak akan berarti. (Jujur, saya hanya akan membuatnya membaca. Jika Anda ingin koneksi dengan string yang berbeda, buat objek lain.)Apakah kita memerlukan antarmuka sama sekali?
Antarmuka yang menentukan pesan yang tersedia yang dapat Anda kirim melalui koneksi dan jenis respons yang bisa Anda dapatkan kembali bisa berguna. Ini akan memungkinkan kita untuk menulis kode yang mengeksekusi operasi ini tetapi tidak digabungkan dengan logika membuka koneksi. Tapi itu intinya: mengelola koneksi bukan bagian dari antarmuka, "Pesan apa yang bisa saya kirim dan pesan apa yang bisa saya dapatkan kembali ke / dari database?", Jadi string koneksi seharusnya tidak menjadi bagian dari itu antarmuka.
Jika kita melewati rute ini, kode kita mungkin terlihat seperti ini:
sumber
Open
metode ini seharusnyaprivate
dan Anda harus mengeksposConnection
properti yang dilindungi yang menciptakan koneksi dan koneksi. Atau memaparkanOpenConnection
metode yang dilindungi .Saya benar-benar tidak melihat alasan memiliki antarmuka sama sekali di sini. Kelas basis data Anda adalah khusus SQL, dan benar-benar hanya memberi Anda cara yang nyaman / aman untuk memastikan Anda tidak menanyakan koneksi yang tidak dibuka dengan benar. Jika Anda bersikeras pada antarmuka, inilah cara saya akan melakukannya.
Penggunaannya mungkin terlihat seperti ini:
sumber