重构为对不同的输入对象使用通用函数

本文关键字:对象 函数 输入 重构 | 更新日期: 2023-09-27 18:14:31

我想重构一些在控制台应用程序中运行的代码。该应用程序更新外部数据库,并且它最近更新以支持MySQL或SQL Server。所以现在有两个几乎相同的方法,其中有很多重复的代码,因为一个方法签名使用MySqlConnection和MySqlCommand(等),另一个使用SqlConnection和SqlCommand(等)。

代码基本上是相同的,除了在ADO对象中有明显的不同。

我想做的是如下所示。我在这里看到了一些关于SO的帖子(例如,我如何使用反射来调用泛型方法?)以及其他展示如何使用动态类型设置这一点的站点,这很好,除了在泛型方法中写入foo.GetType()以证明动态类型是正确的之外,其他示例都没有做任何事情。

如何调用动态类型上的方法?当然,当我尝试设置它时,试图调用sqlConnection参数上的Open()方法不会编译。

这是我想要完成的:

private static void TransferXmlData(ExportManifest m_settings, XmlNodeList xmlNodeList)
{
    if (m_Settings.ServerType.ToLower() == "mysql")
    {
        using (MySqlConnection mySqlConnection = new MySqlConnection(m_Settings.TargetData.ConnectionString))
        {
            MySqlCommand mySqlCommand = 
                new MySqlCommand(Program.GetCommandTextTemplate(m_settings), mySqlConnection);
            PrepareSqlCommand(mySqlConnection, mySqlCommand, m_settings)
        }
    }
    else
    {
        using (SqlConnection sqlConnection = 
            new SqlConnection(m_Settings.TargetData.ConnectionString))
        {
            SqlCommand sqlCommand = 
                new SqlCommand(Program.GetCommandTextTemplate(m_settings), sqlConnection);
            PrepareSqlCommand(sqlConnection, sqlCommand, m_settings)
        }
    }
}
private static void PrepareSqlCommand<T>(T sqlConnection, T sqlCommand, ExportManifest m_settings)
{
    // Potentially a lot of code here that looks just like the 
    // code in the else block, Except that it uses the 
    // MySqlConnection objects instead of SqlConnection
    // Do some stuff
    sqlConnection.Open();  // obviously doesn't work
}

提前感谢!

重构为对不同的输入对象使用通用函数

也许你可以实现工厂设计模式(如果你不想用泛型,这是我的意见,你可以考虑一下。)这将帮助您防止代码重复。

实现你的Factory类

Public class Factory
    {
        public static IDbConnection createDbInstance(ExportManifest m_settings)
        {
            if (m_Settings.ServerType.ToLower() == "mysql")
            {
                 return new MySqlConnection();
            }
            else
               return new SqlConnection();
        }
    } `

,在你的实际方法中,你可以使用IDbConnection和IDbCommand

private static void TransferXmlData(ExportManifest m_settings, XmlNodeList xmlNodeList)
{
    IDbConnection db = Factory.createDbInstance(m_settings);
                db.ConnectionString = m_Settings.TargetData.ConnectionString;
                IDbCommand comnd = db.CreateCommand();
                comnd.CommandText = Program.GetCommandTextTemplate(m_settings);
                comnd.CommandType = CommandType.Text;
               // db.Open(); if you want to open connection here
                PrepareSqlCommand(db, comnd, m_settings);

}

private static void PrepareSqlCommand(IDbConnection sqlConnection, IDbCommand sqlCommand, ExportManifest m_settings)
{
    // Potentially a lot of code here that looks just like the 
    // code in the else block, Except that it uses the 
    // MySqlConnection objects instead of SqlConnection
    // Do some stuff
    sqlConnection.Open(); 
}

为了编写一次数据访问代码,但能够根据某些逻辑切换实现,您应该针对IDbConnection进行编码。

大意是:

using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["AdventureWorks"].ConnectionString))
        {
            //execute database actions against IDBConnection               
        }

在我看来,使用存储库模式可以很好地建立这一点,并且它使您不需要了解数据库实现细节,但是模式的适当实现可能会使您的用例过于复杂。至于决定哪些连接获得new()的逻辑,如上所述的工厂方法就足够了,但如果这是一个微不足道的应用程序,您也可以轻松地传递enum标志。在大型软件中,您通常希望使用控制反转容器来控制要注入的IDbConnection的特定实例。在任何情况下,反射、泛型和动态在这里都是错误的工具(禁止任何对象映射)。

就像@Sehnsucht说的,你可以这样做:

private static void TransferXmlData(ExportManifest m_settings, XmlNodeList xmlNodeList)
{
    if (m_Settings.ServerType.ToLower() == "mysql")
        Connect(connectionString => new MySqlConnection(connectionString),
            (text, connection) => new MySqlCommand(text, connection));
    else
        Connect(connectionString => new SqlConnection(connectionString),
            (text, connection) => new SqlCommand(text, connection));
}
private static void Connect(ExportManifest m_settings,
    Func<string, IDbConnection> createConnection,
        Func<string, IDbConnection, IDbCommand> createCommand)
{
    using (IDbConnection mySqlConnection =
        createConnection(m_Settings.TargetData.ConnectionString))
    {
        IDbCommand mySqlCommand =
            createCommand(Program.GetCommandTextTemplate(m_settings), mySqlConnection);
        PrepareSqlCommand(mySqlConnection, mySqlCommand, m_settings);
    }
}
private static void PrepareSqlCommand(IDbConnection sqlConnection,
    IDbCommand sqlCommand, ExportManifest m_settings)
{
    sqlConnection.Open();
}

SqlConnectionMySqlConnection都继承自DbConnection,实现了IDbConnectionSqlCommandMySqlCommand也是一样,它们实现了IDbCommand
然后,你可以使用这些接口来合并你的代码。


但是,由于某些原因,您将需要使用实际类型(作为返回值)。你可以这样修改你的方法:

private static void Connect<TConnection, TCommand>(ExportManifest m_settings,
        Func<string, TConnection> createConnection,
        Func<string, TConnection, TCommand> createCommand)
    where TConnection : IDbConnection
    where TCommand : IDbCommand
{
    using (TConnection mySqlConnection =
        createConnection(m_Settings.TargetData.ConnectionString))
    {
        TCommand mySqlCommand =
            createCommand(Program.GetCommandTextTemplate(m_settings), mySqlConnection);
        PrepareSqlCommand(mySqlConnection, mySqlCommand, m_settings);
    }
}
private static void PrepareSqlCommand<TConnection, TCommand>(TConnection sqlConnection,
        TCommand sqlCommand, ExportManifest m_settings)
    where TConnection : IDbConnection
    where TCommand : IDbCommand
{
    sqlConnection.Open();
}