web-dev-qa-db-fra.com

Comment spécifier une condition préalable (LSP) dans une interface en C #?

Disons que nous avons l'interface suivante -

interface IDatabase { 
    string ConnectionString{get;set;}
    void ExecuteNoQuery(string sql);
    void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

La condition préalable est que les connexions devaient être définies/supposées avant que l'une des méthodes ne puisse être exécutée.

Cette condition préalable peut être quelque peu obtenue en passant une connexion via un constructeur si l'idatabase était une classe abstraite ou concrète -

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
}

Sinon, nous pouvons créer des connexions d'un paramètre pour chaque méthode, mais cela a l'air pire que de créer une classe abstraite -

interface IDatabase { 
    void ExecuteNoQuery(string connectionString, string sql);
    void ExecuteNoQuery(string connectionString, string[] sql);
    //Various other methods all with the connectionString parameter
}

Des questions -

  1. Existe-t-il un moyen de spécifier cette condition préalable dans l'interface elle-même? C'est un "contrat" ​​valide, donc je me demande s'il existe une caractéristique linguistique ou un modèle pour cela (la solution de classe abstraite est plus d'un piratage imo en plus de la nécessité de créer deux types - une interface et une classe abstraite - chaque fois que Ceci est nécessaire)
  2. C'est plus une curiosité théorique - cette condition préalable tombe-t-elle réellement dans la définition d'une condition préalable comme dans le contexte du LSP?
11
Achilles
  1. Oui. de .NET 4.0 upward, Microsoft fournit Contrats de code . Ceux-ci peuvent être utilisés pour définir des conditions préalables sous la forme Contract.Requires( ConnectionString != null );. Cependant, pour faire ce travail pour une interface, vous aurez toujours besoin d'une classe d'assistance IDatabaseContract, qui devient attaché à IDatabase et la condition préalable doit être définie pour chaque méthode individuelle de votre interface où Il doit tenir. voir ici pour un exemple étendu pour les interfaces.

  2. Oui, le LSP traite des parties syntaxiques et sémantiques d'un contrat.

10
Doc Brown

Les raccordements et les interrogations sont deux préoccupations distinctes. En tant que tel, ils devraient avoir deux interfaces distinctes.

interface IDatabaseConnection
{
    IDatabase Connect(string connectionString);
}

interface IDatabase
{
    public void ExecuteNoQuery(string sql);
    public void ExecuteNoQuery(string[] sql);
}

Cela garantit les deux que IDatabase sera connecté lorsqu'il sera utilisé et que le client ne dépend pas de l'interface, cela n'a pas besoin.

21
Euphoric

Je ne vois vraiment pas la raison d'avoir une interface du tout ici. Votre classe de base de données est spécifique à SQL et vous donne vraiment un moyen pratique/sûr de vous assurer de ne pas interroger sur une connexion qui n'est pas ouverte correctement. Si vous insistez sur une interface cependant, voici comment je le ferais.

public interface IDatabase : IDisposable
{
    string ConnectionString { get; }
    void ExecuteNoQuery(string sql);
    void ExecuteNoQuery(string[] sql);
    //Various other methods all requiring ConnectionString to be set
}

public class SqlDatabase : IDatabase
{
    public string ConnectionString { get; }
    SqlConnection sqlConnection;
    SqlTransaction sqlTransaction; // optional

    public SqlDatabase(string connectionStr)
    {
        if (String.IsNullOrEmpty(connectionStr)) throw new ArgumentException("connectionStr empty");
        ConnectionString = connectionStr;
        instantiateSqlProps();
    }

    private void instantiateSqlProps()
    {
        sqlConnection.Open();
        sqlTransaction = sqlConnection.BeginTransaction();
    }

    public void ExecuteNoQuery(string sql) { /*run query*/ }
    public void ExecuteNoQuery(string[] sql) { /*run query*/ }

    public void Dispose()
    {
        sqlTransaction.Commit();
        sqlConnection.Dispose();
    }

    public void Commit()
    {
        Dispose();
        instantiateSqlProps();
    }
}

L'utilisation pourrait ressembler à ceci:

using (IDatabase dbase = new SqlDatabase("Data Source = servername; Initial Catalog = MyDb; Integrated Security = True"))
{
    dbase.ExecuteNoQuery("delete from dbo.Invoices");
    dbase.ExecuteNoQuery("delete from dbo.Customers");
}
0
Graham