Retrocedamos un paso y miremos la imagen más grande aquí.
¿Cuál es IDatabase
la responsabilidad?
Tiene algunas operaciones diferentes:
- Analizar una cadena de conexión
- Abrir una conexión con una base de datos (un sistema externo)
- Enviar mensajes a la base de datos; los mensajes ordenan a la base de datos que altere su estado
- Reciba respuestas de la base de datos y transfórmelas en un formato que la persona que llama pueda usar
- Cerrar la conexión
Al mirar esta lista, podría estar pensando, "¿No viola esto SRP?" Pero no creo que lo haga. Todas las operaciones son parte de un concepto único y coherente: administrar una conexión con estado a la base de datos (un sistema externo) . Establece la conexión, realiza un seguimiento del estado actual de la conexión (en relación con las operaciones realizadas en otras conexiones, en particular), señala cuándo confirmar el estado actual de la conexión, etc. En este sentido, actúa como una API que oculta muchos detalles de implementación que a la mayoría de las personas que llaman no les importa Por ejemplo, ¿utiliza HTTP, sockets, tuberías, TCP personalizado, HTTPS? Al código de llamada no le importa; solo quiere enviar mensajes y obtener respuestas. Este es un buen ejemplo de encapsulación.
¿Estamos seguros? ¿No podríamos dividir algunas de estas operaciones? Quizás, pero no hay beneficio. Si intenta dividirlos, aún necesitará un objeto central que mantenga la conexión abierta y / o administre cuál es el estado actual. Todas las demás operaciones están fuertemente acopladas al mismo estado, y si intenta separarlas, terminarán delegando de nuevo en el objeto de conexión de todos modos. Estas operaciones están acopladas natural y lógicamente al estado, y no hay forma de separarlas. El desacoplamiento es excelente cuando podemos hacerlo, pero en este caso, en realidad no podemos. Al menos no sin un protocolo sin estado muy diferente para hablar con el DB, y eso en realidad haría que problemas muy importantes como el cumplimiento de ACID sean mucho más difíciles. Además, en el proceso de intentar desacoplar estas operaciones de la conexión, se verá obligado a exponer detalles sobre el protocolo que a las personas que llaman no les importa, ya que necesitará una forma de enviar algún tipo de mensaje "arbitrario" a la base de datos.
Tenga en cuenta que el hecho de que estemos tratando con un protocolo con estado descarta bastante sólidamente su última alternativa (pasar la cadena de conexión como parámetro).
¿Realmente necesitamos establecer una cadena de conexión?
Si. No puede abrir la conexión hasta que tenga una cadena de conexión, y no puede hacer nada con el protocolo hasta que abra la conexión. Por lo tanto, no tiene sentido tener un objeto de conexión sin uno.
¿Cómo resolvemos el problema de requerir la cadena de conexión?
El problema que estamos tratando de resolver es que queremos que el objeto esté en un estado utilizable en todo momento. ¿Qué tipo de entidad se usa para administrar el estado en los idiomas OO? Objetos , no interfaces. Las interfaces no tienen estado para administrar. Debido a que el problema que está tratando de resolver es un problema de administración de estado, una interfaz no es realmente apropiada aquí. Una clase abstracta es mucho más natural. Entonces usa una clase abstracta con un constructor.
También puede considerar abrir realmente la conexión durante el constructor, ya que la conexión también es inútil antes de abrirse. Eso requeriría un protected Open
método abstracto ya que el proceso de abrir una conexión puede ser específico de la base de datos. También sería una buena idea hacer que la ConnectionString
propiedad sea de solo lectura en este caso, ya que cambiar la cadena de conexión después de que la conexión esté abierta no tendría sentido. (Honestamente, lo haría leer de todos modos. Si quieres una conexión con una cadena diferente, crea otro objeto).
¿Necesitamos alguna interfaz?
Puede ser útil una interfaz que especifique los mensajes disponibles que puede enviar a través de la conexión y los tipos de respuestas que puede recibir. Esto nos permitiría escribir código que ejecute estas operaciones pero que no esté acoplado a la lógica de abrir una conexión. Pero ese es el punto: administrar la conexión no es parte de la interfaz de "¿Qué mensajes puedo enviar y qué mensajes puedo volver a / de la base de datos?", Por lo que la cadena de conexión ni siquiera debería ser parte de eso interfaz.
Si seguimos esta ruta, nuestro código podría verse así:
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
}