一种实现方式需要更多数据时该怎么办

时间:2018-09-04 07:24:56

标签: c# oop interface

我在想处理这种情况的正确方法是什么:我有一个接口IService如下所示:

class Configuration
{
    public int Min { get; set; } 
    public int Max { get; set; }     
}

interface IService
{
    int Calculate(int userId, Configuration configuration)
}

我有5个实现此接口的类,它们运行良好。 有一天,我必须实施第六项服务,但是这一点有些不同。要完成其工作,一项新服务需要这样的配置:

class ExtendedConfiguration : Configuration
{
    public string Filter { get; set; }     
}

我的新服务可能如下所示:

class NewService : IService
{
    public int Calculate(int userId, Configuration configuration)
    {
        var extendedConfig = configuration as ExtendedConfiguration;
        //Calculating and returning result using extendedConfig...
    }
}

似乎很好,该服务可以正常工作。 但是,我不喜欢这样的事实,即Calculate方法签名需要Configuration对象,而实际上需要ExtendedConfiguration-否则它将无法进行计算(并会引发异常)。

有没有更好的方式编写此代码?

4 个答案:

答案 0 :(得分:4)

  

计算方法签名需要Configuration对象,而实际上需要ExtendedConfiguration

如上所述,您可以针对您的限制问题寻求通用解决方案。使用通用参数定义接口并将其约束为Configuration类型:

interface IService<T> where T : Configuration
{
    int Calculate(int userId, T configuration)
}

那么您的旧服务仍然看起来像以前那样:

class OldService : IService<Configuration>
{
    public int Calculate(int userId, Configuration configuration)
    {       
        return (configuration.Min + configuration.Max) * 2;
    }
}

,并且在NewService中,您可以指定输入参数必须为ExtendedConfiguration类型:

class NewService : IService<ExtendedConfiguration>
{
    public int Calculate(int userId, ExtendedConfiguration configuration)
    {
        string accessHereTheExtendedVersion = configuration.Filter;
        return (configuration.Min + configuration.Max) / 2;
    }
}

这实际上不是您确切问题的答案:

  

有没有更好的方式编写此代码?

但这是解决此问题的另一种方法。无论它是否适合您的环境和情况,您都必须对其进行测试。

答案 1 :(得分:1)

我想这可以归结为“更好”的定义。我个人不喜欢对ExtendedConfiguration的假定转换。您不仅将如何将该配置填充到其他地方的问题外包了,而且现在如果我发送错误的实现,您的代码将崩溃。因此,外界需要知道您需要此特定的实现,并相应地填充设置值。在代码气味的世界中,这几乎不是死刑,但我会解决这个问题。

提供配置提供程序,而不是为服务提供配置: (使用C#7 ValueTuples

public enum ValueType
{
    ReturnedConfigured,
    NotConfiguredReturnedDefault,
    InvalidConfigurationReturnedDefault
}

public interface IConfigurationProvider
{
    (T result, ValueType resultType) GetSetting<T>(string serviceName, string settingKey, T defaultValue);
}

public interface IService
{
    int Calculate(int userId, IConfigurationProvider configurationProvider);
}

您可以按以下方式使用它:

public class NewService : IService
{
    public int Calculate(int userId, IConfigurationProvider configurationProvider)
    {
        (int min, _) = configurationProvider.GetSetting(nameof(NewService), "Min", -1);
        (int max, _) = configurationProvider.GetSetting(nameof(NewService), "Max", Int32.MaxValue);
        (string filter, ValueType filterConfigResponse) = configurationProvider.GetSetting(nameof(NewService), "Filter", string.Empty);
        if (filterConfigResponse!=ValueType.ReturnedConfigured)
        {
            throw new ArgumentException("Oh no! Where's my filter?", nameof(configurationProvider));
        }
        Console.WriteLine($"{nameof(NewService)},min={min}, max={max}, filter={filter}");
        return 0;
    }
}

这是您可能会注入到单元测试中的IConfigurationProvider的示例

public class FakeConfigurationProvider : IConfigurationProvider
{
    public (T result, ValueType resultType) GetSetting<T>(string serviceName, string settingKey, T defaultValue)
    {
        switch (settingKey)
        {
            case "Min":
                {
                    return (result: (T)Convert.ChangeType(1, typeof(T)), resultType: ValueType.ReturnedConfigured);
                }
            case "Max":
                {
                    return (result: (T)Convert.ChangeType(42, typeof(T)), resultType: ValueType.ReturnedConfigured);
                }
            case "Filter":
                {
                    return (result: (T)Convert.ChangeType("Hello World", typeof(T)), resultType: ValueType.ReturnedConfigured);
                }
            default:
                {
                    return (result: defaultValue, resultType: ValueType.NotConfiguredReturnedDefault);
                }
        }
    }
}

从这里开始,可以想象其他配置提供程序可以从app.Config或数据库表或Uri中提取设置,或者您选择存储它们。

答案 2 :(得分:0)

我认为这个解决方案并不可怕;尝试统一处理继承层次结构中的对象的算法具有缺点,在某些时候,您需要在运行时设置或检查对象的某些特征 。原因正好是您所体验到的:碰到边缘情况,很少有一种解决方案适合所有情况。

检查类型的另一种方法是使Configuration可以可配置。典型的方法是

  • 继承:Configuration定义了额外的虚方法(例如,预处理,额外处理或后处理;-)),这些虚方法为空,但可以在派生类中充满生命。调用代码不必知道确切的类型,它只是愚蠢地以适当的顺序调用这些函数,而特定类定义它们要做的任何事情都可以完成。

  • 注入:Configure对象的行为是在运行时通过设置“知道如何做”的成员来配置的。使用这些“代理”中定义的信息或操作执行计算。

答案 3 :(得分:0)

实际上,没有问题。如前所述,转换失败。

您不想使用泛型类型方法。也许您可以创建一个实例ExtConfigclass并为其分配配置的值。

class NewService : IService
{
    public int Calculate(int userId, Configuration configuration)
    {
        var extendedConfig = new ExtendedConfiguration {
            Max = configuration.Max,
            Min=configuration.Min
        };


        return e.Max - e.Min;
    }
}