El problema
Digamos que tengo una clase llamada DataSourceque proporciona un ReadDatamétodo (y tal vez otros, pero mantengamos las cosas simples) para leer datos de un .mdbarchivo:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Unos años más tarde, decido que quiero poder admitir .xmlarchivos además de .mdbarchivos como fuentes de datos. La implementación de "lectura de datos" es bastante diferente para .xmly .mdbarchivos; así, si tuviera que diseñar el sistema desde cero, lo definiría así:
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 */ }
}
Genial, una implementación perfecta del patrón del método Factory. Desafortunadamente, DataSourcese encuentra en una biblioteca y refactorizar el código de esta manera rompería todas las llamadas existentes de
var source = new DataSource("myFile.mdb");
en los distintos clientes que usan la biblioteca. Ay de mí, ¿por qué no utilicé un método de fábrica en primer lugar?
Soluciones
Estas son las soluciones que podría encontrar:
Hacer que el constructor DataSource devuelva un subtipo (
MdbDataSourceoXmlDataSource). Eso resolvería todos mis problemas. Desafortunadamente, C # no es compatible con eso.Usa diferentes nombres:
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 { ... }Eso es lo que terminé usando, ya que mantiene el código compatible con versiones anteriores (es decir, llamadas para
new DataSource("myFile.mdb")seguir funcionando). Inconveniente: los nombres no son tan descriptivos como deberían ser.Haga
DataSourceun "contenedor" para la implementación real: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 { ... } }Inconveniente: cada método de fuente de datos (como
ReadData) debe ser enrutado por código repetitivo. No me gusta el código repetitivo. Es redundante y desordena el código.
¿Hay alguna solución elegante que me haya perdido?

newno sea un método del objeto de clase (para que pueda subclasificar la clase en sí misma, una técnica conocida como metaclases) y controlar lo quenewrealmente hace, pero no es así como funciona C # (o Java, o C ++).Respuestas:
Buscaría una variante de su segunda opción que le permita eliminar el antiguo nombre, demasiado genérico
DataSource:El único inconveniente aquí es que la nueva clase base no puede tener el nombre más obvio, porque ese nombre ya fue reclamado para la clase original y debe permanecer así para la compatibilidad con versiones anteriores. Todas las otras clases tienen sus nombres descriptivos.
fuente
La mejor solución será algo cercano a su opción # 3. Mantenga la
DataSourcemayoría como está ahora y extraiga solo la parte del lector en su propia clase.De esta manera, evita el código duplicado y está abierto a más extensiones.
fuente
XmlDataSource.