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


12

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){

   }
}

回答:


11

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

そのようです:

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 Injection」などを検討する必要があります。


25

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


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

さらに読む前に、まだ依存関係インジェクションを読んで理解していない場合は、基本的な理解をお勧めします。また、アダプタの設計パターンを確認することもできます。これは、基本的に、インターフェイスのパブリックメソッドの背後に実装の詳細を隠すことを意味します。

Factory設計パターンと組み合わせた依存性注入は、IoC原則の一部であるStrategy設計パターンをコーディングするための基礎となる簡単な方法です。

電話しないでください、電話します。(別名、ハリウッドの原則)。


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

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、楽しみのためにmethodを呼び出しましょう。データを取得したいので、このメソッド内でデータベースを使用する必要があります。

これで、直接作成することのできないインターフェースができました(それでは、どのように使用しますか)がMyMySQLDatabasenewキーワードを使用して構築できるclassがあります。

すごい!データベースを使用したいので、を使用します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個のメソッドで同じことを行うと想定しても安全です。をMyMySQLDatabaseなどの別のクラスMyPostgreSQLDatabaseに変更する場合は、30個すべてのメソッドで変更する必要があります。

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

MyMySQLDatabaseメソッドのパラメーターとして渡すことにより、作成のリファクタリングから始めます(これを依存性注入と呼びます)。

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

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


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

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

ご覧のとおり、今はオブジェクトをSecretMethod使用していMyMySQLDatabaseます。MySQLからMSSQLに移行したと仮定しましょう。内のすべてのロジックを変更する気はありません。パラメーターとして渡された変数のメソッドとメソッドSecretMethodを呼び出すメソッドです。そのため、メソッドとメソッドを持つ新しいクラスを作成します。BeginTransactionCommitTransactiondatabaseMyMSSQLDatabaseBeginTransactionCommitTransaction

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

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

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

えっ、ちょっと待って!

実装するDatabaseインターフェイスとMyMySQLDatabaseMyMSSQLDatabaseクラスとがあります。クラスは、とまったく同じメソッドを持ちますMyMySQLDatabase。おそらく、MSSQLドライバーもDatabaseインターフェイスを実装できるので、定義に追加します。

public class MyMSSQLDatabase : Database { }

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

はい、そうです。そして、それは正しく聞こえません。今、私たちはそれを、知っているMyMSSQLDatabaseMyMySQLDatabase同じメソッドを持っていると実装の両方Databaseのインターフェイスを。このようにリファクタリングSecretMethodします。

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

SecretMethodMySQL、MSSQL、PotgreSQLのどれを使用しているかを知る方法。データベースを使用することは知っていますが、特定の実装については気にしません。

たとえば、PostgreSQLなどの新しいデータベースドライバーを作成する場合はSecretMethod、まったく変更する必要はありません。を作成しMyPostgreSQLDatabaseDatabaseインターフェイスを実装し、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);
    }
}

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

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


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


デビッド、あなたの答えが大好きです。しかし、そのようなすべての答えのように、それをどのように実践するかを説明するには至りません。実際の問題は、さまざまなデータベースエンジンを呼び出す機能を抽象化することではなく、問題は実際のSQL構文です。DbQueryたとえば、オブジェクトを取ります。オブジェクトに実行されるSQLクエリ文字列のメンバーが含まれていると仮定すると、どのようにしてそのジェネリックを作成できますか?
-DonBoitnott

1
@DonBoitnottジェネリックにするためにすべてが必要になるとは思いません。通常、アプリケーションレイヤー(ドメイン、サービス、永続性)の間に抽象化を導入します。また、モジュールの抽象化を導入したい場合があります。 。すべてをインターフェイスに抽象化することもできますが、それはほとんど必要ありません。残念ながら、実際には1つも存在せず、要件に基づいているため、1対1の答えを出すのは非常に困難です。
アンディ

2
わかった。しかし、私は本当に文字通りそれを意味しました。抽象化されたクラスを取得したら、適切なSQLダイアレクトを使用するために_secret.SecretMethod(database);現在作業しているSecretMethodDBをまだ把握している必要があるという事実と、どのように連携するかを呼び出したいポイントに到達します。 ?コードの大部分がその事実を知らないようにするために非常に一生懸命に努力しましたが、11時間目に再び知っておく必要があります。私は現在この状況にあり、他の人がこの問題をどのように解決したかを理解しようとしています。
-DonBoitnott

@DonBoitnott私はあなたが何を意味するのか知らなかった、私は今それを見る。DbQueryクラスの具体的な実装の代わりにインターフェイスを使用し、そのインターフェイスの実装を提供し、代わりにそのインターフェイスを使用して、IDbQueryインスタンスを構築するファクトリを持たせることができます。DatabaseResultクラスにジェネリック型が必要になるとは思わない。データベースからの結果が同様の方法でフォーマットされることを常に期待できる。ここでの事は...データベースと生のSQLを扱う、あなたがする必要がないこと、(DALとリポジトリの背後にある)アプリケーションのような低レベルの上に既にあるとき、ある
アンディ・

...一般的なアプローチ。
アンディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.