Nehmen wir an, wir haben die folgende Schnittstelle -
interface IDatabase {
string ConnectionString{get;set;}
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
Voraussetzung ist, dass ConnectionString gesetzt / initialisiert wird, bevor eine der Methoden ausgeführt werden kann.
Diese Voraussetzung kann etwas erreicht werden, indem ein connectionString über einen Konstruktor übergeben wird, wenn IDatabase eine abstrakte oder konkrete Klasse ist -
abstract class Database {
public string ConnectionString{get;set;}
public Database(string connectionString){ ConnectionString = connectionString;}
public void ExecuteNoQuery(string sql);
public void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
Alternativ können wir für jede Methode einen connectionString-Parameter erstellen, der jedoch schlechter aussieht als nur eine abstrakte Klasse zu erstellen.
interface IDatabase {
void ExecuteNoQuery(string connectionString, string sql);
void ExecuteNoQuery(string connectionString, string[] sql);
//Various other methods all with the connectionString parameter
}
Fragen -
- Gibt es eine Möglichkeit, diese Voraussetzung in der Schnittstelle selbst anzugeben? Da es sich um einen gültigen "Vertrag" handelt, frage ich mich, ob es dafür ein Sprachmerkmal oder -muster gibt (die Lösung für abstrakte Klassen ist eher ein Hack-Imo als die Notwendigkeit, jedes Mal zwei Typen zu erstellen - eine Schnittstelle und eine abstrakte Klasse dies wird benötigt)
- Dies ist eher eine theoretische Kuriosität - Fällt diese Voraussetzung tatsächlich in die Definition einer Vorbedingung wie im Kontext von LSP?
c#
solid
liskov-substitution
Achilles
quelle
quelle
Antworten:
Ja. Ab .NET 4.0 bietet Microsoft Codeverträge an . Diese können verwendet werden, um Voraussetzungen im Formular zu definieren
Contract.Requires( ConnectionString != null );
. Damit dies für eine Schnittstelle funktioniert, benötigen Sie jedoch noch eine HilfsklasseIDatabaseContract
, die angehängt wirdIDatabase
, und die Voraussetzung muss für jede einzelne Methode Ihrer Schnittstelle definiert werden, für die sie gelten soll. Hier finden Sie ein ausführliches Beispiel für Schnittstellen.Ja , der LSP behandelt sowohl syntaktische als auch semantische Teile eines Vertrags.
quelle
Verbinden und Abfragen sind zwei getrennte Anliegen. Als solche sollten sie zwei separate Schnittstellen haben.
Dies stellt sicher, dass
IDatabase
die Verbindung bei Verwendung hergestellt wird, und macht den Client nicht von der Schnittstelle abhängig, die er nicht benötigt.quelle
IDatabase
Schnittstelle definiert ein Objekt, das eine Verbindung zu einer Datenbank herstellen und dann beliebige Abfragen ausführen kann. Es ist das Objekt, das als Grenze zwischen der Datenbank und dem Rest des Codes fungiert. Daher muss dieses Objekt einen Status (z. B. eine Transaktion) beibehalten , der das Verhalten der Abfragen beeinflussen kann. Es ist sehr praktisch, sie in dieselbe Klasse zu bringen.Lassen Sie uns einen Schritt zurücktreten und das Gesamtbild hier betrachten.
Was ist
IDatabase
die Verantwortung?Es hat ein paar verschiedene Operationen:
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:
quelle
Open
Methode sein sollteprivate
und Sie eine geschützteConnection
Eigenschaft verfügbar machen sollten , die die Verbindung herstellt und eine Verbindung herstellt. Oder legen Sie eine geschützteOpenConnection
Methode offen.Ich sehe wirklich keinen Grund, hier überhaupt eine Schnittstelle zu haben. Ihre Datenbankklasse ist SQL-spezifisch und bietet Ihnen nur eine bequeme / sichere Möglichkeit, um sicherzustellen, dass Sie keine Verbindung zu einer Verbindung abfragen, die nicht ordnungsgemäß geöffnet wurde. Wenn Sie jedoch auf einer Schnittstelle bestehen, gehen Sie wie folgt vor.
Die Verwendung könnte folgendermaßen aussehen:
quelle