Le problème
Disons que j'ai une classe appelée DataSource
qui fournit une ReadData
méthode (et peut-être d'autres, mais gardons les choses simples) pour lire les données d'un .mdb
fichier:
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 .xml
fichiers en plus des .mdb
fichiers en tant que sources de données. L'implémentation de la "lecture des données" est très différente pour les fichiers .xml
et .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, DataSource
est 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 (
MdbDataSource
ouXmlDataSource
). 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
DataSource
un "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?
new
ne 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 cenew
qui fonctionne réellement) mais ce n'est pas ainsi que C # (ou Java, ou C ++) fonctionne.Réponses:
J'opterais pour une variante de votre deuxième option qui vous permet de supprimer progressivement l'ancien nom, trop générique
DataSource
:Le seul inconvénient ici est que la nouvelle classe de base ne peut pas avoir le nom le plus évident, car ce nom a déjà été revendiqué pour la classe d'origine et doit rester comme ça pour une compatibilité descendante. Toutes les autres classes ont leurs noms descriptifs.
la source
La meilleure solution sera quelque chose de proche de votre option n ° 3. Conservez le
DataSource
principalement tel qu'il est maintenant et extrayez uniquement la partie lecteur dans sa propre classe.De cette façon, vous évitez le code en double et êtes ouvert à d'autres extensions.
la source
XmlDataSource
.