Prenons un peu de recul et regardons la situation dans son ensemble ici.
Quelle est sa IDatabase
responsabilité?
Il a quelques opérations différentes:
- Analyser une chaîne de connexion
- Ouvrir une connexion avec une base de données (un système externe)
- Envoyer des messages à la base de données; les messages commandent à la base de données de modifier son état
- Recevez les réponses de la base de données et transformez-les en un format que l'appelant peut utiliser
- Fermer la connexion
En regardant cette liste, vous vous demandez peut-être: "Est-ce que cela ne viole pas le SRP?" Mais je ne pense pas. Toutes les opérations font partie d'un concept unique et cohérent: gérer une connexion avec état à la base de données (un système externe) . Il établit la connexion, garde une trace de l'état actuel de la connexion (par rapport aux opérations effectuées sur d'autres connexions notamment), il signale quand valider l'état actuel de la connexion, etc. En ce sens, il agit comme une API qui cache beaucoup de détails d'implémentation dont la plupart des appelants ne se soucient pas. Par exemple, utilise-t-il HTTP, sockets, pipes, TCP personnalisé, HTTPS? Le code d'appel ne se soucie pas; il veut juste envoyer des messages et obtenir des réponses. Ceci est un bon exemple d'encapsulation.
Sommes-nous sûrs? Ne pourrions-nous pas fractionner certaines de ces opérations? Peut-être, mais il n'y a aucun avantage. Si vous essayez de les diviser, vous aurez toujours besoin d'un objet central qui maintient la connexion ouverte et / ou gère l'état actuel. Toutes les autres opérations sont fortement couplées au même état, et si vous essayez de les séparer, elles finiront tout de même par déléguer à nouveau l'objet de connexion. Ces opérations sont naturellement et logiquement couplées à l'État, et il n'y a aucun moyen de les séparer. Le découplage est formidable lorsque nous pouvons le faire, mais dans ce cas, nous ne pouvons pas réellement. Du moins pas sans un protocole sans état très différent pour parler à la base de données, et cela rendrait en fait les problèmes très importants comme la conformité ACID beaucoup plus difficiles. De plus, dans le processus de découplage de ces opérations de la connexion, vous serez obligé d'exposer des détails sur le protocole dont les appelants ne se soucient pas, car vous aurez besoin d'un moyen d'envoyer une sorte de message "arbitraire". à la base de données.
Notez que le fait que nous ayons affaire à un protocole avec état exclut assez solidement votre dernière alternative (passer la chaîne de connexion comme paramètre).
Avons-nous vraiment besoin que la chaîne de connexion soit définie?
Oui. Vous ne pouvez pas ouvrir la connexion avant d'avoir une chaîne de connexion et vous ne pouvez rien faire avec le protocole tant que vous n'avez pas ouvert la connexion. Il est donc inutile d'avoir un objet de connexion sans un.
Comment résoudre le problème de la nécessité de la chaîne de connexion?
Le problème que nous essayons de résoudre est que nous voulons que l'objet soit à tout moment utilisable. Quel type d'entité est utilisé pour gérer l'état dans les langues OO? Des objets , pas des interfaces. Les interfaces n'ont pas d'état à gérer. Parce que le problème que vous essayez de résoudre est un problème de gestion d'état, une interface n'est pas vraiment appropriée ici. Une classe abstraite est beaucoup plus naturelle. Utilisez donc une classe abstraite avec un constructeur.
Vous pouvez également envisager d' ouvrir réellement la connexion pendant le constructeur, car la connexion est également inutile avant son ouverture. Cela nécessiterait une protected Open
méthode abstraite car le processus d'ouverture d'une connexion peut être spécifique à la base de données. Ce serait également une bonne idée de rendre la ConnectionString
propriété en lecture seule dans ce cas, car changer la chaîne de connexion une fois la connexion ouverte n'aurait aucun sens. (Honnêtement, je le ferais lire de toute façon. Si vous voulez une connexion avec une chaîne différente, créez un autre objet.)
Avons-nous besoin d'une interface?
Une interface qui spécifie les messages disponibles que vous pouvez envoyer via la connexion et les types de réponses que vous pouvez obtenir pourrait être utile. Cela nous permettrait d'écrire du code qui exécute ces opérations mais n'est pas couplé à la logique d'ouverture d'une connexion. Mais c'est le point: la gestion de la connexion ne fait pas partie de l'interface de "Quels messages puis-je envoyer et quels messages puis-je retourner vers / depuis la base de données?", Donc la chaîne de connexion ne devrait même pas en faire partie. interface.
Si nous empruntons cette voie, notre code pourrait ressembler à ceci:
interface IDatabase {
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
abstract class ConnectionStringDatabase : IDatabase {
public string ConnectionString { get; }
public Database(string connectionString) {
this.ConnectionString = connectionString;
this.Open();
}
protected abstract void Open();
public abstract void ExecuteNoQuery(string sql);
public abstract void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}