Masalah
Katakanlah saya memiliki kelas bernama DataSource
yang menyediakan ReadData
metode (dan mungkin yang lain, tapi mari kita tetap sederhana) untuk membaca data dari .mdb
file:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Beberapa tahun kemudian, saya memutuskan bahwa saya ingin dapat mendukung .xml
file selain .mdb
file sebagai sumber data. Implementasi untuk "membaca data" sangat berbeda untuk .xml
dan .mdb
file; jadi, jika saya mendesain sistem dari awal, saya akan mendefinisikannya seperti ini:
abstract class DataSource {
abstract Data ReadData();
static DataSource OpenDataSource(string fileName) {
// return MdbDataSource or XmlDataSource, as appropriate
}
}
class MdbDataSource : DataSource {
override Data ReadData() { /* implementation 1 */ }
}
class XmlDataSource : DataSource {
override Data ReadData() { /* implementation 2 */ }
}
Hebat, implementasi sempurna dari pola metode Pabrik. Sayangnya, DataSource
terletak di perpustakaan dan refactoring kode seperti ini akan memecah semua panggilan yang ada
var source = new DataSource("myFile.mdb");
di berbagai klien menggunakan perpustakaan. Celakalah saya, mengapa saya tidak menggunakan metode pabrik di tempat pertama?
Solusi
Ini adalah solusi yang bisa saya buat:
Buat konstruktor DataSource mengembalikan subtipe (
MdbDataSource
atauXmlDataSource
). Itu akan menyelesaikan semua masalah saya. Sayangnya, C # tidak mendukung itu.Gunakan nama yang berbeda:
abstract class DataSourceBase { ... } // corresponds to DataSource in the example above class DataSource : DataSourceBase { // corresponds to MdbDataSource in the example above [Obsolete("New code should use DataSourceBase.OpenDataSource instead")] DataSource(string fileName) { ... } ... } class XmlDataSource : DataSourceBase { ... }
Itulah yang akhirnya saya gunakan karena menjaga kode yang kompatibel ke belakang (yaitu panggilan untuk
new DataSource("myFile.mdb")
tetap bekerja). Kekurangan: Nama tidak deskriptif seperti yang seharusnya.Buat
DataSource
"pembungkus" untuk implementasi nyata:class DataSource { private DataSourceImpl impl; DataSource(string fileName) { impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName); } Data ReadData() { return impl.ReadData(); } abstract private class DataSourceImpl { ... } private class MdbDataSourceImpl : DataSourceImpl { ... } private class XmlDataSourceImpl : DataSourceImpl { ... } }
Kelemahan: Setiap metode sumber data (seperti
ReadData
) harus dirutekan oleh kode boilerplate. Saya tidak suka kode boilerplate. Itu berlebihan dan mengacaukan kode.
Apakah ada solusi elegan yang saya lewatkan?
new
bukan metode objek kelas (sehingga Anda bisa subkelas kelas itu sendiri - suatu teknik yang dikenal sebagai metaclasses - dan mengontrol apa yangnew
sebenarnya) tetapi itu bukan cara kerja C # (atau Java, atau C ++).Jawaban:
Saya akan memilih varian untuk opsi kedua Anda yang memungkinkan Anda menghapus nama lama, terlalu umum
DataSource
:Satu-satunya kelemahan di sini adalah bahwa kelas dasar baru tidak dapat memiliki nama yang paling jelas, karena nama itu sudah diklaim untuk kelas asli dan perlu tetap seperti itu untuk kompatibilitas ke belakang. Semua kelas lain memiliki nama deskriptif.
sumber
Solusi terbaik akan menjadi sesuatu yang dekat dengan pilihan Anda # 3. Simpan
DataSource
sebagian besar seperti sekarang, dan ekstrak hanya bagian pembaca ke dalam kelasnya sendiri.Dengan cara ini, Anda menghindari kode duplikat, dan terbuka untuk ekstensi lebih lanjut.
sumber
XmlDataSource
.