O problema
Digamos que eu tenho uma classe chamada DataSourceque fornece um ReadDatamétodo (e talvez outros, mas vamos simplificar) para ler dados de um .mdbarquivo:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Alguns anos depois, decido que quero poder suportar .xmlarquivos além de .mdbarquivos como fontes de dados. A implementação para "ler dados" é bem diferente para os arquivos .xmle .mdb; portanto, se eu fosse projetar o sistema a partir do zero, eu o definiria assim:
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 */ }
}
Ótimo, uma implementação perfeita do padrão de método Factory. Infelizmente, DataSourceestá localizado em uma biblioteca e refatorar o código dessa forma interromperia todas as chamadas existentes de
var source = new DataSource("myFile.mdb");
nos vários clientes que usam a biblioteca. Ai de mim, por que não usei um método de fábrica?
Soluções
Estas são as soluções que eu poderia encontrar:
Faça o construtor DataSource retornar um subtipo (
MdbDataSourceouXmlDataSource). Isso resolveria todos os meus problemas. Infelizmente, o C # não suporta isso.Use nomes diferentes:
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 { ... }Foi o que acabei usando, pois mantém o código compatível com versões anteriores (ou seja, as chamadas para
new DataSource("myFile.mdb")continuar funcionando). Desvantagem: os nomes não são tão descritivos quanto deveriam ser.Faça
DataSourceum "wrapper" para a implementação 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 { ... } }Desvantagem: todo método de fonte de dados (como
ReadData) deve ser roteado pelo código padrão. Eu não gosto de código padrão. É redundante e desorganiza o código.
Existe alguma solução elegante que eu perdi?
newnão seja um método do objeto de classe (para que você possa subclassificar a própria classe - uma técnica conhecida como metaclasses - e controlar o que newrealmente faz), mas não é assim que funciona o C # (ou Java ou C ++).