Vamos dar um passo atrás e ver a foto maior aqui.
Qual é IDatabase
a responsabilidade?
Possui algumas operações diferentes:
- Analisar uma cadeia de conexão
- Abrir uma conexão com um banco de dados (um sistema externo)
- Envie mensagens para o banco de dados; as mensagens comandam o banco de dados para alterar seu estado
- Receba respostas do banco de dados e transforme-as em um formato que o chamador possa usar
- Feche a conexão
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 a ConnectionString
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:
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
}