Le problème
Disons que j'ai une classe appelée DataSourcequi fournit une ReadDataméthode (et peut-être d'autres, mais gardons les choses simples) pour lire les données d'un .mdbfichier:
var source = new DataSource("myFile.mdb");
var data = source.ReadData();
Quelques années plus tard, je décide que je veux pouvoir prendre en charge des .xmlfichiers en plus des .mdbfichiers en tant que sources de données. L'implémentation de la "lecture des données" est très différente pour les fichiers .xmlet .mdb; ainsi, si je devais concevoir le système à partir de zéro, je le définirais comme ceci:
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 */ }
}
Génial, une implémentation parfaite du modèle de méthode Factory. Malheureusement, DataSourceest situé dans une bibliothèque et refactoriser le code comme celui-ci romprait tous les appels existants de
var source = new DataSource("myFile.mdb");
dans les différents clients utilisant la bibliothèque. Malheur à moi, pourquoi n'ai-je pas utilisé une méthode d'usine en premier lieu?
Solutions
Voici les solutions que je pourrais trouver:
Faites en sorte que le constructeur DataSource renvoie un sous-type (
MdbDataSourceouXmlDataSource). Cela résoudrait tous mes problèmes. Malheureusement, C # ne prend pas en charge cela.Utilisez des noms différents:
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 { ... }C'est ce que j'ai fini par utiliser car il maintient le code rétrocompatible (c'est-à-dire que les appels
new DataSource("myFile.mdb")fonctionnent toujours). Inconvénient: les noms ne sont pas aussi descriptifs qu'ils devraient l'être.Faites
DataSourceun "wrapper" pour la vraie implémentation: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 { ... } }Inconvénient: chaque méthode de source de données (telle que
ReadData) doit être acheminée par du code passe-partout. Je n'aime pas le code passe-partout. C'est redondant et encombre le code.
Y a-t-il une solution élégante que j'ai ratée?
newne soit pas une méthode de l'objet classe (afin que vous puissiez sous-classer la classe elle-même - une technique connue sous le nom de métaclasses - et contrôler ce newqui fonctionne réellement) mais ce n'est pas ainsi que C # (ou Java, ou C ++) fonctionne.