Digamos que tenemos la siguiente interfaz:
interface IDatabase {
string ConnectionString{get;set;}
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
La condición previa es que ConnectionString debe establecerse / inicializarse antes de poder ejecutar cualquiera de los métodos.
Esta condición previa se puede lograr de alguna manera al pasar un connectionString a través de un constructor si IDatabase fuera una clase abstracta u concreta:
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
}
Alternativamente, podemos crear connectionString un parámetro para cada método, pero se ve peor que simplemente crear una clase abstracta:
interface IDatabase {
void ExecuteNoQuery(string connectionString, string sql);
void ExecuteNoQuery(string connectionString, string[] sql);
//Various other methods all with the connectionString parameter
}
Preguntas
- ¿Hay alguna manera de especificar esta condición previa dentro de la interfaz misma? Es un "contrato" válido, así que me pregunto si hay una característica o patrón de lenguaje para esto (la solución de clase abstracta es más un truco, además de la necesidad de crear dos tipos - una interfaz y una clase abstracta - cada vez esto es necesario)
- Esto es más una curiosidad teórica: ¿esta condición previa cae realmente en la definición de una condición previa como en el contexto de LSP?
c#
solid
liskov-substitution
Aquiles
fuente
fuente
Respuestas:
Si. Desde .Net 4.0 en adelante, Microsoft proporciona contratos de código . Estos se pueden utilizar para definir condiciones previas en el formulario
Contract.Requires( ConnectionString != null );
. Sin embargo, para que esto funcione para una interfaz, aún necesitará una clase auxiliarIDatabaseContract
, que se adjuntaráIDatabase
, y la condición previa debe definirse para cada método individual de su interfaz donde se mantendrá. Vea aquí un amplio ejemplo de interfaces.Sí , el LSP trata con partes sintácticas y semánticas de un contrato.
fuente
Conectarse y consultar son dos preocupaciones separadas. Como tal, deben tener dos interfaces separadas.
Esto garantiza que
IDatabase
se conectará cuando se use y hace que el cliente no dependa de la interfaz que no necesita.fuente
IDatabase
interfaz define un objeto capaz de establecer una conexión a una base de datos y luego ejecutar consultas arbitrarias. Que es el objeto que actúa como el límite entre la base de datos y el resto del código. Como tal, este objeto tiene que mantener un estado (como una transacción) que pueda afectar el comportamiento de las consultas. Ponerlos en la misma clase es muy práctico.Retrocedamos un paso y miremos la imagen más grande aquí.
¿Cuál es
IDatabase
la responsabilidad?Tiene algunas operaciones diferentes:
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 laConnectionString
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í:
fuente
Open
método debería serprivate
y usted debería exponer unaConnection
propiedad protegida que crea la conexión y se conecta. O exponer unOpenConnection
método protegido .Realmente no veo la razón para tener una interfaz aquí. Su clase de base de datos es específica de SQL, y realmente le brinda una manera conveniente / segura de asegurarse de que no está consultando en una conexión que no se abre correctamente. Sin embargo, si insiste en una interfaz, así es como lo haría.
El uso podría verse así:
fuente