采取婴儿步骤应用更好的设计

时间:2009-12-11 15:16:19

标签: c# configuration refactoring dependency-injection separation-of-concerns

在想要获得良好OO设计的实践经验时,我决定尝试在遗留应用上应用关注点分离。

我觉得我对这些遍布代码库的调用感到不舒服。

ConfigurationManager.AppSettings["key"]

虽然我之前已经通过编写一个辅助类来将这些调用封装到静态方法中来解决这个问题,但我认为这可能是一个进一步发展的机会。

我意识到最终我的目标应该是使用依赖注入并始终是'编码到接口'。但我不想采取看起来太大的一步。与此同时,我希望朝着最终目标迈出更小的步伐。

  

任何人都可以列举他们推荐的步骤吗?

以下是一些浮现在脑海中的想法:

  • 客户端代码依赖于接口而非具体实现

  • 手动将依赖项注入 通过构造函数或属性接口?

  • 在努力之前 选择并应用IoC 容器如何保留代码 运行

  • 为了履行默认的依赖关系 任何需要的类的构造函数 配置值可以使用Factory (使用静态CreateObject()方法)?

当然,我仍然会对工厂有一个具体的依赖?...

我已经进入Michael Feathers' book所以我知道我需要引入接缝,但我很难知道我什么时候引入了足够的或者太多了!

更新

想象一下,Client调用WidgetLoader上的方法,向它传递所需的依赖项(例如IConfigReader)

WidgetLoader读取配置以找出要加载的小部件,并要求WidgetFactory依次创建每个小部件

WidgetFactory读取配置以了解默认情况下将Widgets放入的状态

WidgetFactory委托WidgetRepository进行数据访问,读取config以决定应记录哪些诊断

  

在上面的每种情况下,IConfigReader应该像调用链中每个成员之间的烫手山芋一样传递吗?

     

工厂是答案吗?

澄清以下一些评论:

我的主要目标是逐步将一些应用程序设置从配置文件迁移到其他形式的持久性。虽然我意识到,通过注入依赖关系,我可以Extract and Override来获得一些单元测试的优点,但我主要关心的是测试不足以封装足以开始不知道设置实际持续存在的位置。

3 个答案:

答案 0 :(得分:3)

在重构遗留代码库时,您希望迭代地随时间进行小的更改。这是一种方法:

  • 使用获取应用设置的方法创建一个新的静态类(即MyConfigManager)(即GetAppSettingString(字符串键)

  • 执行全局搜索并替换“ConfigurationManager.AppSettings [”key“]并将实例替换为”MyConfigManager.GetAppSettingsString(“key”)“

  • 测试和登记

现在,您对ConfigurationManager的依赖性在一个地方。您可以将设置存储在数据库中或任何位置,而无需更改大量代码。缺点是你仍然有静态依赖。

下一步是将MyConfigManager更改为常规实例类,并将其注入使用它的类中。这里最好的方法是逐步进行。

  • 在静态类旁边创建一个实例类(和一个接口)。

  • 既然你有两个,你可以慢慢地重构使用类,直到他们都使用实例类。将实例注入构造函数(使用接口)。如果有很多用法,请不要尝试大爆炸登记入住。随着时间的推移,慢慢小心地做。

  • 然后只需删除静态类。

答案 1 :(得分:1)

通常很难清理遗留应用程序是一个小步骤,因为它们不是以这种方式改变的。如果代码完全混合并且你没有SoC,那么很难在没有被迫改变其他东西的情况下改变它...而且通常很难对任何东西进行单元测试。

但总的来说,你必须: 1)找到尚未重构的最简单(最小)的类 2)为这个类编写单元测试,这样你就可以确信你的重构没有破坏任何东西 3)尽可能小的改变(这取决于项目和你的常识) 4)确保所有测试都通过 5)提交并转到1

我想推荐Martin Fowler的“重构”给你更多的想法:http://www.amazon.com/exec/obidos/ASIN/0201485672

答案 2 :(得分:1)

对于您的示例,我要做的第一件事就是创建一个界面,展示您阅读配置所需的功能,例如。

public interface IConfigReader
{
    string GetAppSetting(string key);
    ...
}

然后创建一个委托给静态ConfigurationManager类的实现:

public class StaticConfigReader : IConfigReader
{
    public string Get(string key)
    {
        return ConfigurationManager.AppSetting[key];
    }
}

然后,对于依赖于配置的特定类,您可以创建一个最初只返回静态配置读取器实例的接缝:

public class ClassRequiringConfig
{
    public void MethodUsingConfig()
    {
        string setting = this.GetConfigReader().GetAppSetting("key");
    }
    protected virtual IConfigReader GetConfigReader()
    {
        return new StaticConfigReader();
    }
}

并将所有对ConfigManager的引用替换为您的界面的用法。然后出于测试目的,您可以继承此类并重写GetConfigReader方法以注入假货,因此您不需要任何实际的配置文件:

public class TestClassRequiringConfig : ClassRequiringConfig
{
    public IConfigReader ConfigReader { get; set; }
    protected override IConfigReader GetConfigReader()
    {
        return this.ConfigReader;
    }
}

[Test]
public void TestMethodUsingConfig()
{
    ClassRequiringConfig sut = new TestClassRequiringConfig { ConfigReader = fakeConfigReader };
    sut.MethodUsingConfig();

    //Assertions
}

然后,当您添加IoC容器时,最终可以用属性/构造函数注入替换它。

编辑: 如果您不满意将实例注入到这样的单个类中(如果许多类依赖于配置将会非常繁琐),那么您可以创建一个静态配置类,然后允许临时更改配置读取器以进行测试:

    public static class Configuration
{
    private static Func<IConfigReader> _configReaderFunc = () => new StaticConfigReader;

    public static Func<IConfigReader> GetConfiguration
    {
        get { return _configReaderFunc; }
    }

    public static IDisposable CreateConfigScope(IConfigReader reader)
    {
        return new ConfigReaderScope(() => reader);
    }

    private class ConfigReaderScope : IDisposable
    {
        private readonly Func<IConfigReader> _oldReaderFunc;

        public ConfigReaderScope(Func<IConfigReader> newReaderFunc)
        {
            this._oldReaderFunc = _configReaderFunc;
            _configReaderFunc = newReaderFunc;
        }

        public void Dispose()
        {
            _configReaderFunc = this._oldReaderFunc;
        }
    }
}

然后你的类只通过静态类访问配置:

public void MethodUsingConfig()
{
    string value = Configuration.GetConfigReader().GetAppSetting("key");
}

并且您的测试可以通过临时范围使用假冒:

[Test]
public void TestMethodUsingConfig()
{
    using(var scope = Configuration.CreateConfigScope(fakeReader))
    {
        new ClassUsingConfig().MethodUsingConfig();
        //Assertions
    }
}
相关问题