web-dev-qa-db-ja.com

抽象データベースインターフェイスは、複数のデータベースタイプをサポートするためにどのように記述されていますか?

MySQL、SQLLite、MSSQLなどの複数のタイプのデータベースとインターフェースできる、より大きなアプリケーションで抽象クラスを設計するにはどうすればよいですか?

このデザインパターンは何と呼ばれ、正確にどこから始まりますか?

次のメソッドを持つクラスを書く必要があるとしましょう

public class Database {
   public DatabaseType databaseType;
   public Database (DatabaseType databaseType){
      this.databaseType = databaseType;
   }

   public void SaveToDatabase(){
       // Save some data to the db
   }
   public void ReadFromDatabase(){
      // Read some data from db
   }
}

//Application
public class Foo {
    public Database db = new Database (DatabaseType.MySQL);
    public void SaveData(){
        db.SaveToDatabase();
    }
}

私が考えられる唯一のことは、すべてのDatabaseメソッドのifステートメントです

public void SaveToDatabase(){
   if(databaseType == DatabaseType.MySQL){

   }
   else if(databaseType == DatabaseType.SQLLite){

   }
}
12
tones31

必要なのは、アプリケーションが使用するinterface複数の実装です。

そのようです:

public interface IDatabase
{
    void SaveToDatabase();
    void ReadFromDatabase();
}

public class MySQLDatabase : IDatabase
{
   public MySQLDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //MySql implementation
   }
   public void ReadFromDatabase(){
      //MySql implementation
   }
}

public class SQLLiteDatabase : IDatabase
{
   public SQLLiteDatabase ()
   {
      //init stuff
   }

   public void SaveToDatabase(){
       //SQLLite implementation
   }
   public void ReadFromDatabase(){
      //SQLLite implementation
   }
}

//Application
public class Foo {
    public IDatabase db = GetDatabase();

    public void SaveData(){
        db.SaveToDatabase();
    }

    private IDatabase GetDatabase()
    {
        if(/*some way to tell if should use MySql*/)
            return new MySQLDatabase();
        else if(/*some way to tell if should use MySql*/)
            return new SQLLiteDatabase();

        throw new Exception("You forgot to configure the database!");
    }
}

アプリケーションで実行時に正しいIDatabase実装を設定するより良い方法に関しては、「Factory Method」や「Dependancy注射

11
Caleb

カレブの答えは、彼が正しい道を進んでいる間、実際には間違っています。彼のFooクラスは、データベースのファサードとファクトリーの両方として機能します。これらは2つの責任であり、単一のクラスに分類するべきではありません。


この質問は、特にデータベースのコンテキストでは、何度も尋ねられました。ここでは、抽象化を使用して(インターフェースを使用して)アプリケーションの結合を少なくし、用途を広げる利点を徹底的に示します。

さらに読む前に、 依存性注入 を読んで基本的な理解を深めることをお勧めします。また、 Adapter design pattern を確認することもできます。これは、基本的に、インターフェースのパブリックメソッドの背後にある実装の詳細を非表示にすることです。

Factoryデザインパターン と組み合わせた依存性注入は、基盤であり、一部である Strategy design pattern をコーディングする簡単な方法です IoC原則

私たちに電話しないでください、私たちはあなたに電話します。 (別名 ハリウッドの原理 )。


抽象化を使用したアプリケーションの分離

1.抽象化レイヤーの作成

インターフェース、またはC++のような言語でコーディングしている場合は抽象クラスを作成し、このインターフェースにジェネリックメソッドを追加します。インターフェースと抽象クラスの両方には、それらを直接使用できないという動作がありますが、それらを実装する(インターフェースの場合)または拡張する(抽象クラ​​スの場合)必要があるため、コード自体はすでに示唆しています。インターフェイスまたは抽象クラスのいずれかによって与えられたコントラクトを完全に満たすための特定の実装が必要です。

(非常に単純な例の)データベースインターフェースは次のようになります(DatabaseResultまたはDbQueryクラスは、それぞれデータベース操作を表す独自の実装になります)。

public interface Database
{
    DatabaseResult DoQuery(DbQuery query);
    void BeginTransaction();
    void RollbackTransaction();
    void CommitTransaction();
    bool IsInTransaction();
}

これはインターフェースなので、それ自体は実際には何もしません。したがって、このインターフェースを実装するクラスが必要です。

public class MyMySQLDatabase : Database
{
    private readonly CSharpMySQLDriver _mySQLDriver;

    public MyMySQLDatabase(CSharpMySQLDriver mySQLDriver)
    {
        _mySQLDriver = mySQLDriver;
    }

    public DatabaseResult DoQuery(DbQuery query)
    {
        // This is a place where you will use _mySQLDriver to handle the DbQuery
    }

    public void BeginTransaction()
    {
        // This is a place where you will use _mySQLDriver to begin transaction
    }

    public void RollbackTransaction()
    {
    // This is a place where you will use _mySQLDriver to rollback transaction
    }

    public void CommitTransaction()
    {
    // This is a place where you will use _mySQLDriver to commit transaction
    }

    public bool IsInTransaction()
    {
    // This is a place where you will use _mySQLDriver to check, whether you are in a transaction
    }
}

これで、Databaseを実装するクラスが作成されました。インターフェイスが便利になりました。

2.抽象化レイヤーの使用

アプリケーションのどこかに、メソッドがあります。楽しみのためにSecretMethodメソッドを呼び出しましょう。このメソッド内では、一部のデータをフェッチするため、データベースを使用する必要があります。

これで、直接作成することはできません(ええと、どうやってそれを使うのですか)のインターフェイスがありますが、MyMySQLDatabaseキーワードを使用して構築できるクラスnewがあります。

GREAT!データベースを使用したいので、MyMySQLDatabaseを使用します。

メソッドは次のようになります。

public void SecretMethod()
{
    var database = new MyMySQLDatabase(new CSharpMySQLDriver());

    // you will use the database here, which has the DoQuery,
    // BeginTransaction, RollbackTransaction and CommitTransaction methods
}

これは良くない。このメソッド内でクラスを直接作成しています。SecretMethod内でクラスを作成している場合、他の30のメソッドでも同じことを行うと想定しても安全です。 MyMySQLDatabaseMyPostgreSQLDatabaseなどの別のクラスに変更する場合は、30のメソッドすべてで変更する必要があります。

別の問題は、MyMySQLDatabaseの作成が失敗した場合、メソッドが終了せず、無効になることです。

最初に、MyMySQLDatabaseの作成をパラメーターとしてメソッドに渡すことにより、これをリファクタリングします(これは依存関係注入と呼ばれます)。

public void SecretMethod(MyMySQLDatabase database)
{
    // use the database here
}

これにより、MyMySQLDatabaseオブジェクトを作成できないという問題が解決されます。 SecretMethodは有効なMyMySQLDatabaseオブジェクトを想定しているため、何かが発生してオブジェクトが渡されない場合、メソッドは実行されません。そして、それはまったく問題ありません。


一部のアプリケーションでは、これで十分な場合があります。あなたは満足しているかもしれませんが、それをさらに改善するためにリファクタリングしましょう。

別のリファクタリングの目的

ご覧のとおり、SecretMethodMyMySQLDatabaseオブジェクトを使用しています。 MySQLからMSSQLに移行したとします。パラメータとして渡されたSecretMethod変数でBeginTransactionメソッドとCommitTransactionメソッドを呼び出すメソッドであるdatabase内のすべてのロジックを変更したくないので、MyMSSQLDatabaseメソッドとBeginTransactionメソッドを持つ新しいクラスCommitTransactionを作成します。

次に、SecretMethodの宣言を次のように変更します。

public void SecretMethod(MyMSSQLDatabase database)
{
    // use the database here
}

また、クラスMyMSSQLDatabaseMyMySQLDatabaseは同じメソッドを持っているため、他に何も変更する必要はなく、引き続き機能します。

あ、待って!

DatabaseインターフェースがMyMySQLDatabaseに実装されており、MyMSSQLDatabaseクラスもMyMySQLDatabaseとまったく同じメソッドを持っています。MSSQLドライバーがDatabaseインターフェースも実装している可能性があるため、定義に追加します。

public class MyMSSQLDatabase : Database { }

しかし、PostgreSQLに切り替えたために、将来MyMSSQLDatabaseを使用したくない場合はどうなりますか?もう一度、SecretMethodの定義を置き換える必要があります

はい、そうです。そして、それは正しく聞こえません。現在、MyMSSQLDatabaseMyMySQLDatabaseには同じメソッドがあり、どちらもDatabaseインターフェースを実装していることがわかりました。したがって、SecretMethodを次のようにリファクタリングします。

public void SecretMethod(Database database)
{
    // use the database here
}

MySQL、MSSQL、またはPotgreSQLのいずれを使用しているかにかかわらず、SecretMethodがどのように認識されないかに注意してください。データベースを使用していることはわかっていますが、特定の実装については考慮していません。

PostgreSQLなどの新しいデータベースドライバーを作成する場合は、SecretMethodを変更する必要はありません。 MyPostgreSQLDatabaseを作成し、Databaseインターフェースを実装します。PostgreSQLドライバーのコーディングが完了して機能するようになったら、インスタンスを作成してSecretMethodに挿入します。

3. Databaseの望ましい実装を取得する

SecretMethodを呼び出す前に、必要なDatabaseインターフェースの実装(MySQL、MSSQL、PostgreSQLのいずれであるか)を決定する必要があります。これには、工場設計パターンを使用できます。

public class DatabaseFactory
{
    private Config _config;

    public DatabaseFactory(Config config)
    {
        _config = config;
    }

    public Database getDatabase()
    {
        var databaseType = _config.GetDatabaseType();

        Database database = null;

        switch (databaseType)
        {
        case DatabaseEnum.MySQL:
            database = new MyMySQLDatabase(new CSharpMySQLDriver());
            break;
        case DatabaseEnum.MSSQL:
            database = new MyMSSQLDatabase(new CSharpMSSQLDriver());
            break;
        case DatabaseEnum.PostgreSQL:
            database = new MyPostgreSQLDatabase(new CSharpPostgreSQLDriver());
            break;
        default:
            throw new DatabaseDriverNotImplementedException();
            break;
        }

        return database;
    }
}

ご覧のように、ファクトリーは構成ファイルから使用するデータベースのタイプを認識しています(ここでも、Configクラスは独自の実装である場合があります)。

理想的には、DatabaseFactoryを依存関係注入コンテナー内に配置します。プロセスは次のようになります。

public class ProcessWhichCallsTheSecretMethod
{
    private DIContainer _di;
    private ClassWithSecretMethod _secret;

    public ProcessWhichCallsTheSecretMethod(DIContainer di, ClassWithSecretMethod secret)
    {
        _di = di;
        _secret = secret;
    }

    public void TheProcessMethod()
    {
        Database database = _di.Factories.DatabaseFactory.getDatabase();
        _secret.SecretMethod(database);
    }
}

プロセス内のどこに特定のデータベースタイプを作成していないか見てください。それだけでなく、何も作成していません。依存関係注入コンテナー(_di変数)内に格納されているGetDatabaseオブジェクトでDatabaseFactoryメソッドを呼び出しています。これは、構成に基づいてDatabaseインターフェイスの正しいインスタンスを返すメソッドです。

PostgreSQLを3週間使用した後、MySQLに戻りたい場合は、単一の構成ファイルを開き、DatabaseDriverフィールドの値をDatabaseEnum.PostgreSQLからDatabaseEnum.MySQLに変更します。これで完了です。突然、アプリケーションの残りの部分は、1行を変更することにより、MySQLを正しく正しく使用します。


それでも驚かない場合は、IoCについてもう少し詳しく説明することをお勧めします。構成からではなく、ユーザー入力から特定の決定を行う方法。このアプローチは戦略パターンと呼ばれ、エンタープライズアプリケーションで使用できますが、使用されていますが、コンピューターゲームを開発する場合にははるかに頻繁に使用されます。

25
Andy