Проблема
Скажімо, у мене є клас під назвою, DataSource
який забезпечує ReadData
метод (а може бути й інші, але давайте будемо робити прості речі) для читання даних з .mdb
файлу:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Через кілька років я вирішую, що хочу підтримувати .xml
файли на додаток до .mdb
файлів як джерел даних. Реалізація для «читання даних» дуже різна для .xml
і .mdb
файлів; таким чином, якби я проектував систему з нуля, я б визначив її так:
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 */ }
}
Чудово, досконала реалізація схеми фабричного методу. На жаль, DataSource
розташований у бібліотеці, і рефакторинг коду таким чином порушив би всі існуючі виклики
var source = new DataSource("myFile.mdb");
у різних клієнтів, що користуються бібліотекою. Горе мені, чому я в першу чергу не застосував заводський метод?
Рішення
Це рішення, які я міг би придумати:
Змусити конструктор DataSource повернути підтип (
MdbDataSource
абоXmlDataSource
). Це вирішило б усі мої проблеми. На жаль, C # не підтримує цього.Використовуйте різні назви:
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 { ... }
Це те, що я в кінцевому підсумку використав, оскільки він підтримує код, сумісний із зворотним боком (тобто дзвінки
new DataSource("myFile.mdb")
все ще працюють). Недолік: Назви не такі описові, як повинні бути.Зробіть
DataSource
"обгортку" для реальної реалізації: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 { ... } }
Недолік: Кожен метод джерела даних (наприклад
ReadData
) повинен бути направлений кодовою табличкою. Мені не подобається код котла. Це зайве і захаращує код.
Чи є якесь елегантне рішення, яке я пропустив?
new
це не метод об’єкта класу (щоб ви могли підкласирувати сам клас - техніку, відому як метакласи - і контролювати, що new
насправді робить), але це не так, як працює C # (або Java, або C ++).