卡勒布(Caleb)的步伐正确,但实际上是错误的。他的Foo
班级既充当数据库外观又充当工厂。这是两项职责,不应放在同一类中。
这个问题,特别是在数据库环境中,已经被问了太多遍了。在这里,我将尽力向您展示使用抽象(使用接口)的好处,以使您的应用程序更少耦合,功能更广泛。
在进一步阅读之前,建议您阅读并获得对依赖注入的基本了解,如果您还不了解的话。您可能还需要检查Adapter设计模式,这基本上是将实现细节隐藏在接口的公共方法后面的意思。
依赖注入与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
为好玩,而在此方法内,您必须使用数据库,因为您要获取一些数据。
现在,您有了一个不能直接创建的接口(呃,那我该如何使用它),但是却有了一个class MyMySQLDatabase
,可以使用new
关键字构造它。
大!我想使用数据库,所以我将使用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
and CommitTransaction
方法database
,因此您将创建一个新类MyMSSQLDatabase
,该类也具有BeginTransaction
and 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
field 的值从更改DatabaseEnum.PostgreSQL
为DatabaseEnum.MySQL
。您完成了。突然,应用程序的其余部分通过更改一行来再次正确使用MySQL。
如果您仍然不感到惊讶,我建议您多花一点时间研究IoC。如何不根据配置而是根据用户输入做出某些决定。这种方法称为策略模式,尽管可以并且已在企业应用程序中使用,但在开发计算机游戏时更经常使用。
DbQuery
对象为例。假设该对象包含要执行的SQL查询字符串的成员,那么如何使它通用?