Facciamo un passo indietro e guardiamo l'immagine più grande qui.
Qual è IDatabase
la responsabilità?
Ha alcune operazioni diverse:
- Analizzare una stringa di connessione
- Apri una connessione con un database (un sistema esterno)
- Invia messaggi al database; i messaggi comandano al database di modificarne lo stato
- Ricevi le risposte dal database e trasformale in un formato utilizzabile dal chiamante
- Chiudi la connessione
Guardando questo elenco, potresti pensare: "Questo non viola SRP?" Ma non credo. Tutte le operazioni fanno parte di un unico concetto coerente: gestire una connessione stateful al database (un sistema esterno) . Stabilisce la connessione, tiene traccia dello stato corrente della connessione (in relazione alle operazioni eseguite su altre connessioni, in particolare), segnala quando eseguire il commit dello stato corrente della connessione, ecc. In questo senso, funge da API che nasconde molti dettagli di implementazione di cui la maggior parte dei chiamanti non si preoccuperà. Ad esempio, utilizza HTTP, socket, pipe, TCP personalizzato, HTTPS? Chiamare il codice non importa; vuole solo inviare messaggi e ottenere risposte. Questo è un buon esempio di incapsulamento.
Siamo sicuri? Non potremmo dividere alcune di queste operazioni? Forse, ma non ci sono benefici. Se provi a dividerli, avrai comunque bisogno di un oggetto centrale che mantenga aperta la connessione e / o gestisca lo stato corrente. Tutte le altre operazioni sono fortemente accoppiate allo stesso stato e, se si tenta di separarle, finiranno comunque per delegare nuovamente l'oggetto di connessione. Queste operazioni sono naturalmente e logicamente accoppiate allo stato e non c'è modo di separarle. Il disaccoppiamento è fantastico quando possiamo farlo, ma in questo caso, in realtà non possiamo. Almeno non senza un protocollo stateless molto diverso per parlare con il DB, e ciò renderebbe molto più difficili problemi molto importanti come la conformità ACID. Inoltre, nel tentativo di disaccoppiare queste operazioni dalla connessione, sarai costretto a esporre dettagli sul protocollo che non interessano ai chiamanti, poiché avrai bisogno di un modo per inviare un tipo di messaggio "arbitrario" al database.
Nota che il fatto che abbiamo a che fare con un protocollo con stato esclude in modo abbastanza solido la tua ultima alternativa (passando la stringa di connessione come parametro).
Abbiamo davvero bisogno di impostare una stringa di connessione?
Sì. Non è possibile aprire la connessione finché non si dispone di una stringa di connessione e non è possibile eseguire alcuna operazione con il protocollo finché non si apre la connessione. Quindi è inutile avere un oggetto connessione senza uno.
Come risolviamo il problema di richiedere la stringa di connessione?
Il problema che stiamo cercando di risolvere è che vogliamo che l'oggetto sia sempre utilizzabile. Che tipo di entità viene utilizzata per gestire lo stato nelle lingue OO? Oggetti , non interfacce. Le interfacce non hanno stato da gestire. Poiché il problema che stai cercando di risolvere è un problema di gestione dello stato, un'interfaccia non è davvero appropriata qui. Una classe astratta è molto più naturale. Quindi usa una classe astratta con un costruttore.
Potresti anche considerare di aprire effettivamente la connessione anche durante il costruttore, poiché la connessione è anche inutile prima che venga aperta. Ciò richiederebbe un protected Open
metodo astratto poiché il processo di apertura di una connessione potrebbe essere specifico del database. Sarebbe anche una buona idea far ConnectionString
leggere la proprietà solo in questo caso, poiché cambiare la stringa di connessione dopo l'apertura della connessione non avrebbe senso. (Onestamente, lo farei leggere solo in ogni caso. Se vuoi una connessione con una stringa diversa, crea un altro oggetto.)
Abbiamo bisogno di un'interfaccia?
Un'interfaccia che specifica i messaggi disponibili che è possibile inviare tramite la connessione e i tipi di risposte che è possibile ottenere potrebbero essere utili. Ciò ci consentirebbe di scrivere codice che esegue queste operazioni ma non è associato alla logica di apertura di una connessione. Ma questo è il punto: la gestione della connessione non fa parte dell'interfaccia di "Quali messaggi posso inviare e quali messaggi posso tornare al / dal database?", Quindi la stringa di connessione non dovrebbe far parte di questo interfaccia.
Se seguiamo questa strada, il nostro codice potrebbe assomigliare a questo:
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
}