Facciamo un passo indietro e guardiamo l'immagine più grande qui.
Qual è la responsabilità di IDatabase
?
Ha alcune operazioni diverse:
- Analizza una stringa di connessione
- Apri una connessione con un database (un sistema esterno)
- inviare messaggi al database; i messaggi comandano al database di alterarne lo stato
- Ricevi risposte dal database e trasformale in un formato che il chiamante può utilizzare
- Chiudi la connessione
Guardando questo elenco, potresti pensare: "Questo non viola l'SRP?" Ma non penso che lo faccia. Tutte le operazioni fanno parte di un unico concetto coeso: 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 commettere lo 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 cura. Ad esempio, usa HTTP, socket, pipe, TCP personalizzato, HTTPS? Chiamare il codice non interessa; vuole solo inviare messaggi e ottenere risposte. Questo è un buon esempio di incapsulamento.
Siamo sicuri? Non potremmo suddividere alcune di queste operazioni? Forse, ma non c'è alcun vantaggio. Se provi a dividerli, avrai comunque bisogno di un oggetto centrale che mantenga la connessione aperta e / o che controlli lo stato corrente. Tutte le altre operazioni sono strongmente accoppiate allo stesso stato, e se provate a separarle, finiranno comunque per delegare nuovamente all'oggetto di connessione. Queste operazioni sono naturalmente e logicamente accoppiate allo stato, e non c'è modo di separarle. Il disaccoppiamento è ottimo 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 questo renderebbe molto più difficili problemi molto importanti come la compliance ACID. Inoltre, nel tentativo di disaccoppiare queste operazioni dalla connessione, sarai costretto ad esporre dettagli sul protocollo a cui i chiamanti non interessano, dal momento che avrai bisogno di un modo per inviare una sorta di messaggio "arbitrario" al database.
Si noti che il fatto che abbiamo a che fare con un protocollo stateful esclude abbastanza decisamente l'ultima alternativa (passare la stringa di connessione come parametro).
Abbiamo veramente bisogno che la stringa di connessione sia impostata?
Sì. Non puoi aprire la connessione finché non hai una stringa di connessione e non puoi fare nulla con il protocollo finché non apri la connessione. Quindi è inutile avere un oggetto di 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 in uno stato utilizzabile. Che tipo di entità viene utilizzata per gestire lo stato nelle lingue OO? Oggetti , non interfacce. Le interfacce non hanno lo stato da gestire. Poiché il problema che stai cercando di risolvere è un problema di gestione dello stato, un'interfaccia non è proprio appropriata qui. Una classe astratta è molto più naturale. Quindi usa una classe astratta con un costruttore.
Potresti anche prendere in considerazione aprire la connessione anche durante il costruttore, poiché anche la connessione è inutile prima di essere aperta. Ciò richiederebbe un metodo protected Open
astratto poiché il processo di apertura di una connessione potrebbe essere specifico del database. Sarebbe anche una buona idea rendere la proprietà ConnectionString
di sola lettura in questo caso, poiché modificare la stringa di connessione dopo che la connessione è stata aperta non avrebbe avuto senso. (Onestamente, lo farei comunque solo leggere. 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 puoi inviare tramite la connessione e i tipi di risposte che puoi recuperare potrebbero essere utili. Ciò consentirebbe di scrivere codice che esegue queste operazioni ma non è accoppiato alla logica di apertura di una connessione. Ma questo è il punto: gestire la connessione non fa parte dell'interfaccia di "Quali messaggi posso inviare e quali messaggi posso tornare dal / al database?", Quindi la stringa di connessione non dovrebbe nemmeno far parte di quella interfaccia.
Se seguiamo questa strada, il nostro codice potrebbe essere simile 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
}