Digamos que temos a seguinte interface -
interface IDatabase {
string ConnectionString{get;set;}
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
A pré-condição é que ConnectionString deve ser definido / inicializado antes que qualquer um dos métodos possa ser executado.
Essa pré-condição pode ser um pouco alcançada passando uma connectionString por meio de um construtor se o IDatabase fosse uma classe abstrata ou 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
}
Como alternativa, podemos criar um parâmetro connectionString para cada método, mas parece pior do que apenas criar uma classe abstrata -
interface IDatabase {
void ExecuteNoQuery(string connectionString, string sql);
void ExecuteNoQuery(string connectionString, string[] sql);
//Various other methods all with the connectionString parameter
}
Questões -
- Existe uma maneira de especificar essa pré-condição na própria interface? É um "contrato" válido, então estou me perguntando se existe um recurso ou padrão de linguagem para isso (a solução de classe abstrata é mais uma imitação de hack, além da necessidade de criar dois tipos - uma interface e uma classe abstrata - sempre isso é necessário)
- Isso é mais uma curiosidade teórica - essa pré-condição realmente se enquadra na definição de uma pré-condição como no contexto do LSP?
c#
solid
liskov-substitution
Aquiles
fonte
fonte
Respostas:
Sim. A partir do .Net 4.0, a Microsoft fornece contratos de código . Eles podem ser usados para definir pré-condições no formulário
Contract.Requires( ConnectionString != null );
. No entanto, para fazer isso funcionar para uma interface, você ainda precisará de uma classe auxiliarIDatabaseContract
, a qual é anexadaIDatabase
, e a pré-condição precisa ser definida para cada método individual da sua interface onde ela deve se manter. Veja aqui um exemplo abrangente de interfaces.Sim , o LSP lida com as partes sintática e semântica de um contrato.
fonte
Conectar e consultar são duas preocupações distintas. Como tal, eles devem ter duas interfaces separadas.
Isso garante que o dispositivo
IDatabase
será conectado quando usado e faz com que o cliente não dependa da interface de que não precisa.fonte
IDatabase
interface define um objeto capaz de estabelecer uma conexão com um banco de dados e, em seguida, executar consultas arbitrárias. Ele é o objeto que atua como a fronteira entre o banco de dados e o resto do código. Como tal, esse objeto deve manter o estado (como uma transação) que pode afetar o comportamento das consultas. Colocá-los na mesma classe é muito prático.Vamos dar um passo atrás e ver a foto maior aqui.
Qual é
IDatabase
a responsabilidade?Possui algumas operações diferentes:
Olhando para esta lista, você pode estar pensando: "Isso não viola o SRP?" Mas acho que não. Todas as operações fazem parte de um conceito único e coeso: gerenciar uma conexão estável com o banco de dados (um sistema externo) . Estabelece a conexão, mantém o controle do estado atual da conexão (em relação às operações realizadas em outras conexões, em particular), sinaliza quando confirmar o estado atual da conexão, etc. Nesse sentido, atua como uma API que oculta muitos detalhes de implementação com os quais a maioria dos chamadores não se importa. Por exemplo, ele usa HTTP, soquetes, tubulações, TCP personalizado, HTTPS? Código de chamada não se importa; ele só quer enviar mensagens e obter respostas. Este é um bom exemplo de encapsulamento.
Temos certeza? Não poderíamos dividir algumas dessas operações? Talvez, mas não há benefício. Se você tentar dividi-los, ainda precisará de um objeto central que mantenha a conexão aberta e / ou gerencie qual é o estado atual. Todas as outras operações estão fortemente acopladas ao mesmo estado e, se você tentar separá-las, elas acabarão delegando de volta ao objeto de conexão de qualquer maneira. Essas operações são natural e logicamente acopladas ao estado, e não há como separá-las. A dissociação é excelente quando podemos fazê-lo, mas, neste caso, na verdade não podemos. Pelo menos não sem um protocolo muito diferente e sem estado para conversar com o banco de dados, e isso realmente dificultaria muito os problemas muito importantes, como a conformidade com ACID. Além disso, no processo de tentar desacoplar essas operações da conexão, você será forçado a expor detalhes sobre o protocolo de que os chamadores não se importam, pois você precisará de uma maneira de enviar algum tipo de mensagem "arbitrária" para o banco de dados.
Observe que o fato de estarmos lidando com um protocolo estável exclui bastante sua última alternativa (passar a cadeia de conexão como um parâmetro).
Realmente precisamos que a cadeia de conexão seja definida?
Sim. Você não pode abrir a conexão até ter uma string de conexão e não pode fazer nada com o protocolo até abrir a conexão. Portanto, é inútil ter um objeto de conexão sem um.
Como resolvemos o problema de exigir a cadeia de conexão?
O problema que estamos tentando resolver é que queremos que o objeto esteja em um estado utilizável o tempo todo. Que tipo de entidade é usada para gerenciar o estado nos idiomas OO? Objetos , não interfaces. As interfaces não têm estado para gerenciar. Como o problema que você está tentando resolver é um problema de gerenciamento de estado, uma interface não é realmente apropriada aqui. Uma classe abstrata é muito mais natural. Portanto, use uma classe abstrata com um construtor.
Você também pode considerar abrir a conexão durante o construtor, já que a conexão também é inútil antes de ser aberta. Isso exigiria um
protected Open
método abstrato , pois o processo de abertura de uma conexão pode ser específico do banco de dados. Também seria uma boa idéia fazer com que aConnectionString
propriedade fosse lida apenas nesse caso, pois alterar a cadeia de conexão após a conexão ser aberta não faria sentido. (Honestamente, eu faria apenas leitura de qualquer maneira. Se você quiser uma conexão com uma string diferente, crie outro objeto.)Precisamos de uma interface?
Uma interface que especifica as mensagens disponíveis que você pode enviar pela conexão e os tipos de respostas que você pode receber de volta podem ser úteis. Isso nos permitiria escrever código que executa essas operações, mas não está acoplado à lógica de abrir uma conexão. Mas esse é o ponto: gerenciar a conexão não faz parte da interface de "Que mensagens posso enviar e quais mensagens posso voltar para / do banco de dados?", Portanto, a cadeia de conexão nem deve fazer parte disso. interface.
Se seguirmos essa rota, nosso código poderá ser algo assim:
fonte
Open
método deveria serprivate
e você deveria expor umaConnection
propriedade protegida que cria a conexão e se conecta. Ou exponha umOpenConnection
método protegido .Realmente não vejo o motivo de ter uma interface aqui. Sua classe de banco de dados é específica do SQL e realmente oferece uma maneira conveniente / segura de garantir que você não esteja consultando uma conexão que não foi aberta corretamente. Se você insistir em uma interface, aqui está como eu faria isso.
O uso pode ser assim:
fonte