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){
}
}
必要なのは、アプリケーションが使用する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注射。
カレブの答えは、彼が正しい道を進んでいる間、実際には間違っています。彼のFoo
クラスは、データベースのファサードとファクトリーの両方として機能します。これらは2つの責任であり、単一のクラスに分類するべきではありません。
この質問は、特にデータベースのコンテキストでは、何度も尋ねられました。ここでは、抽象化を使用して(インターフェースを使用して)アプリケーションの結合を少なくし、用途を広げる利点を徹底的に示します。
さらに読む前に、 依存性注入 を読んで基本的な理解を深めることをお勧めします。また、 Adapter design pattern を確認することもできます。これは、基本的に、インターフェースのパブリックメソッドの背後にある実装の詳細を非表示にすることです。
Factoryデザインパターン と組み合わせた依存性注入は、基盤であり、一部である Strategy design pattern をコーディングする簡単な方法です IoC原則 。
私たちに電話しないでください、私たちはあなたに電話します。 (別名 ハリウッドの原理 )。
インターフェース、または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
を実装するクラスが作成されました。インターフェイスが便利になりました。
アプリケーションのどこかに、メソッドがあります。楽しみのために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のメソッドでも同じことを行うと想定しても安全です。 MyMySQLDatabase
をMyPostgreSQLDatabase
などの別のクラスに変更する場合は、30のメソッドすべてで変更する必要があります。
別の問題は、MyMySQLDatabase
の作成が失敗した場合、メソッドが終了せず、無効になることです。
最初に、MyMySQLDatabase
の作成をパラメーターとしてメソッドに渡すことにより、これをリファクタリングします(これは依存関係注入と呼ばれます)。
public void SecretMethod(MyMySQLDatabase database)
{
// use the database here
}
これにより、MyMySQLDatabase
オブジェクトを作成できないという問題が解決されます。 SecretMethod
は有効なMyMySQLDatabase
オブジェクトを想定しているため、何かが発生してオブジェクトが渡されない場合、メソッドは実行されません。そして、それはまったく問題ありません。
一部のアプリケーションでは、これで十分な場合があります。あなたは満足しているかもしれませんが、それをさらに改善するためにリファクタリングしましょう。
別のリファクタリングの目的
ご覧のとおり、SecretMethod
はMyMySQLDatabase
オブジェクトを使用しています。 MySQLからMSSQLに移行したとします。パラメータとして渡されたSecretMethod
変数でBeginTransaction
メソッドとCommitTransaction
メソッドを呼び出すメソッドであるdatabase
内のすべてのロジックを変更したくないので、MyMSSQLDatabase
メソッドとBeginTransaction
メソッドを持つ新しいクラスCommitTransaction
を作成します。
次に、SecretMethod
の宣言を次のように変更します。
public void SecretMethod(MyMSSQLDatabase database)
{
// use the database here
}
また、クラスMyMSSQLDatabase
とMyMySQLDatabase
は同じメソッドを持っているため、他に何も変更する必要はなく、引き続き機能します。
Database
インターフェースがMyMySQLDatabase
に実装されており、MyMSSQLDatabase
クラスもMyMySQLDatabase
とまったく同じメソッドを持っています。MSSQLドライバーがDatabase
インターフェースも実装している可能性があるため、定義に追加します。
public class MyMSSQLDatabase : Database { }
しかし、PostgreSQLに切り替えたために、将来MyMSSQLDatabase
を使用したくない場合はどうなりますか?もう一度、SecretMethod
?の定義を置き換える必要があります
はい、そうです。そして、それは正しく聞こえません。現在、MyMSSQLDatabase
とMyMySQLDatabase
には同じメソッドがあり、どちらもDatabase
インターフェースを実装していることがわかりました。したがって、SecretMethod
を次のようにリファクタリングします。
public void SecretMethod(Database database)
{
// use the database here
}
MySQL、MSSQL、またはPotgreSQLのいずれを使用しているかにかかわらず、SecretMethod
がどのように認識されないかに注意してください。データベースを使用していることはわかっていますが、特定の実装については考慮していません。
PostgreSQLなどの新しいデータベースドライバーを作成する場合は、SecretMethod
を変更する必要はありません。 MyPostgreSQLDatabase
を作成し、Database
インターフェースを実装します。PostgreSQLドライバーのコーディングが完了して機能するようになったら、インスタンスを作成してSecretMethod
に挿入します。
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についてもう少し詳しく説明することをお勧めします。構成からではなく、ユーザー入力から特定の決定を行う方法。このアプローチは戦略パターンと呼ばれ、エンタープライズアプリケーションで使用できますが、使用されていますが、コンピューターゲームを開発する場合にははるかに頻繁に使用されます。