カレブの答えは、彼が正しい軌道に乗っている間、実際は間違っています。彼の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を呼び出しましょう。データを取得したいので、このメソッド内でデータベースを使用する必要があります。
これで、直接作成することのできないインターフェースができました(それでは、どのように使用しますか)がMyMySQLDatabase
、new
キーワードを使用して構築できる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
を呼び出すメソッドです。そのため、メソッドとメソッドを持つ新しいクラスを作成します。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 { }
しかし、将来、MyMSSQLDatabase
PostgreSQLに切り替えたために、もう使用したくない場合はどうでしょうか。もう一度、SecretMethod
?の定義を置き換える必要があります。
はい、そうです。そして、それは正しく聞こえません。今、私たちはそれを、知っているMyMSSQLDatabase
とMyMySQLDatabase
同じメソッドを持っていると実装の両方Database
のインターフェイスを。このようにリファクタリングSecretMethod
します。
public void SecretMethod(Database database)
{
// use the database here
}
SecretMethod
MySQL、MSSQL、PotgreSQLのどれを使用しているかを知る方法。データベースを使用することは知っていますが、特定の実装については気にしません。
たとえば、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);
}
}
見て、プロセスのどこに特定のデータベースタイプを作成しているのか。それだけでなく、何も作成していません。依存関係注入コンテナー(変数)内に格納さGetDatabase
れているDatabaseFactory
オブジェクト_di
のメソッド、つまりDatabase
、構成に基づいてインターフェイスの正しいインスタンスを返すメソッドを呼び出しています。
PostgreSQLを使用して3週間後にMySQLに戻りたい場合、単一の構成ファイルを開き、DatabaseDriver
フィールドの値をからDatabaseEnum.PostgreSQL
に変更しますDatabaseEnum.MySQL
。これで完了です。突然、1行変更するだけで、アプリケーションの残りの部分が再びMySQLを正しく使用するようになります。
それでも驚かない場合は、IoCにもう少し飛び込むことをお勧めします。構成からではなく、ユーザー入力から特定の決定を行う方法。このアプローチは戦略パターンと呼ばれ、エンタープライズアプリケーションで使用できますが、使用されていますが、コンピューターゲームを開発する際に非常に頻繁に使用されます。
DbQuery
たとえば、オブジェクトを取ります。オブジェクトに実行されるSQLクエリ文字列のメンバーが含まれていると仮定すると、どのようにしてそのジェネリックを作成できますか?