Проблема
Допустим, у меня есть класс с именем, 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 ++).Ответы:
Я бы выбрал вариант для вашего второго варианта, который позволит вам отказаться от старого, слишком общего имени
DataSource
:Единственным недостатком здесь является то, что новый базовый класс не может иметь наиболее очевидное имя, потому что это имя уже было заявлено для исходного класса и должно оставаться таким же для обратной совместимости. Все остальные классы имеют свои описательные имена.
источник
Лучшим решением будет что-то близкое к вашему варианту №3. Сохраняйте в
DataSource
основном то, что есть сейчас, и извлекайте только часть читателя в свой класс.Таким образом, вы избегаете дублирования кода и открыты для дальнейших расширений.
источник
XmlDataSource
.