Lassen Sie uns einen Schritt zurücktreten und das Gesamtbild hier betrachten.
Was ist IDatabase
die Verantwortung?
Es hat ein paar verschiedene Operationen:
- Analysieren Sie eine Verbindungszeichenfolge
- Öffnen Sie eine Verbindung mit einer Datenbank (einem externen System).
- Nachrichten an die Datenbank senden; Die Nachrichten befehlen der Datenbank, ihren Status zu ändern
- Empfangen Sie Antworten aus der Datenbank und wandeln Sie sie in ein Format um, das der Anrufer verwenden kann
- Schließen Sie die Verbindung
Wenn Sie sich diese Liste ansehen, denken Sie vielleicht: "Verstößt dies nicht gegen SRP?" Aber ich glaube nicht. Alle Vorgänge sind Teil eines einzigen zusammenhängenden Konzepts: Verwalten einer zustandsbehafteten Verbindung zur Datenbank (einem externen System) . Es stellt die Verbindung her, verfolgt den aktuellen Status der Verbindung (insbesondere in Bezug auf Operationen, die an anderen Verbindungen ausgeführt werden), signalisiert, wann der aktuelle Status der Verbindung festgeschrieben werden soll usw. In diesem Sinne fungiert es als API Das verbirgt viele Implementierungsdetails, die den meisten Anrufern egal sind. Verwendet es beispielsweise HTTP, Sockets, Pipes, benutzerdefiniertes TCP und HTTPS? Das Aufrufen des Codes ist egal; Es möchte nur Nachrichten senden und Antworten erhalten. Dies ist ein gutes Beispiel für die Einkapselung.
Sind wir sicher Könnten wir einige dieser Operationen nicht aufteilen? Vielleicht, aber es gibt keinen Vorteil. Wenn Sie versuchen, sie aufzuteilen, benötigen Sie weiterhin ein zentrales Objekt, das die Verbindung offen hält und / oder den aktuellen Status verwaltet. Alle anderen Vorgänge sind stark an denselben Status gekoppelt. Wenn Sie versuchen, sie zu trennen, werden sie ohnehin nur an das Verbindungsobjekt zurückdelegiert. Diese Operationen sind natürlich und logisch an den Zustand gekoppelt, und es gibt keine Möglichkeit, sie zu trennen. Entkopplung ist großartig, wenn wir es schaffen, aber in diesem Fall können wir es tatsächlich nicht. Zumindest nicht ohne ein ganz anderes, zustandsloses Protokoll, um mit der DB zu sprechen, und das würde tatsächlich sehr wichtige Probleme wie die ACID-Konformität viel schwieriger machen. Wenn Sie versuchen, diese Vorgänge von der Verbindung zu entkoppeln, müssen Sie außerdem Details zu dem Protokoll offenlegen, das Anrufern egal ist, da Sie eine Art "willkürliche" Nachricht senden müssen in die Datenbank.
Beachten Sie, dass die Tatsache, dass es sich um ein Stateful-Protokoll handelt, Ihre letzte Alternative (Übergabe der Verbindungszeichenfolge als Parameter) ziemlich solide ausschließt.
Müssen wir wirklich eine Verbindungszeichenfolge setzen?
Ja. Sie können die Verbindung erst öffnen , wenn Sie eine Verbindungszeichenfolge haben, und Sie können nichts mit dem Protokoll tun, bis Sie die Verbindung öffnen. Es ist also sinnlos , ein Verbindungsobjekt ohne eines zu haben.
Wie lösen wir das Problem, dass die Verbindungszeichenfolge erforderlich ist?
Das Problem, das wir zu lösen versuchen, ist, dass das Objekt jederzeit in einem verwendbaren Zustand sein soll. Welche Art von Entität wird zum Verwalten des Status in OO-Sprachen verwendet? Objekte , keine Schnittstellen. Schnittstellen müssen nicht verwaltet werden. Da das Problem, das Sie lösen möchten, ein Problem der Statusverwaltung ist, ist eine Schnittstelle hier nicht wirklich geeignet. Eine abstrakte Klasse ist viel natürlicher. Verwenden Sie also eine abstrakte Klasse mit einem Konstruktor.
Möglicherweise möchten Sie auch in Betracht ziehen, die Verbindung auch während des Konstruktors zu öffnen , da die Verbindung auch vor dem Öffnen unbrauchbar ist. Dies würde eine abstrakte protected Open
Methode erfordern , da der Prozess des Öffnens einer Verbindung datenbankspezifisch sein kann. ConnectionString
In diesem Fall ist es auch eine gute Idee, die Eigenschaft schreibgeschützt zu machen , da das Ändern der Verbindungszeichenfolge nach dem Öffnen der Verbindung bedeutungslos wäre. (Ehrlich gesagt würde ich es sowieso nur lesbar machen. Wenn Sie eine Verbindung mit einer anderen Zeichenfolge wünschen, erstellen Sie ein anderes Objekt.)
Benötigen wir überhaupt eine Schnittstelle?
Eine Schnittstelle, die die verfügbaren Nachrichten angibt, die Sie über die Verbindung senden können, und die Arten von Antworten, die Sie zurückerhalten können, kann hilfreich sein. Dies würde es uns ermöglichen, Code zu schreiben, der diese Operationen ausführt, aber nicht an die Logik des Öffnens einer Verbindung gekoppelt ist. Aber das ist der Punkt: Die Verwaltung der Verbindung ist nicht Teil der Schnittstelle von "Welche Nachrichten kann ich senden und welche Nachrichten kann ich zur / von der Datenbank zurückholen?", Daher sollte die Verbindungszeichenfolge nicht einmal Teil davon sein Schnittstelle.
Wenn wir diesen Weg gehen, könnte unser Code ungefähr so aussehen:
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
}