El problema
Digamos que tengo una clase llamada DataSource
que proporciona un ReadData
método (y tal vez otros, pero mantengamos las cosas simples) para leer datos de un .mdb
archivo:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Unos años más tarde, decido que quiero poder admitir .xml
archivos además de .mdb
archivos como fuentes de datos. La implementación de "lectura de datos" es bastante diferente para .xml
y .mdb
archivos; 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, DataSource
se 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 (
MdbDataSource
oXmlDataSource
). 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
DataSource
un "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?
new
no 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 quenew
realmente 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
DataSource
mayorí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
.