O problema
Digamos que eu tenho uma classe chamada DataSource
que fornece um ReadData
método (e talvez outros, mas vamos simplificar) para ler dados de um .mdb
arquivo:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Alguns anos depois, decido que quero poder suportar .xml
arquivos além de .mdb
arquivos como fontes de dados. A implementação para "ler dados" é bem diferente para os arquivos .xml
e .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, DataSource
está 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 (
MdbDataSource
ouXmlDataSource
). 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
DataSource
um "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?
new
nã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 quenew
realmente faz), mas não é assim que funciona o C # (ou Java ou C ++).Respostas:
Eu usaria uma variante para sua segunda opção que permite eliminar gradualmente o nome antigo e genérico demais
DataSource
:A única desvantagem aqui é que a nova classe base não pode ter o nome mais óbvio, porque esse nome já foi reivindicado para a classe original e precisa permanecer assim para compatibilidade com versões anteriores. Todas as outras classes têm seus nomes descritivos.
fonte
A melhor solução será algo próximo à sua opção nº 3. Mantenha a
DataSource
maioria como está agora e extraia apenas a parte do leitor em sua própria classe.Dessa forma, você evita códigos duplicados e está aberto a outras extensões.
fonte
XmlDataSource
.