【问题标题】:What to do when one of implementations requires a bit more data当其中一种实现需要更多数据时该怎么办
【发布时间】:2018-09-04 07:24:56
【问题描述】:

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

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

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

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

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 - 否则它将无法进行计算(并且会抛出异常)。

有没有更好的方法来编写这段代码?

【问题讨论】:

  • 如何使接口通用?然后IService<TConfig> 可以在Calculate 方法中采用TConfig configuration 参数。
  • 您可以通过在界面中使用泛型来实现这一点。类似于int Calculate(int userId, T configuration) 和T 的东西在接口定义级别被限制为Configuration 类型。
  • "(并且会抛出异常)。"仅供参考configuration as ExtendedConfiguration 不会引发异常。如果转换失败,它将返回 null。你可以检查一下。
  • 这个问题的问题是Is there a better way of writing this code? 取决于很多其他因素,你提到工厂而不喜欢泛型。我觉得你已经回答了你自己的问题。我能想到的唯一其他事情是,制作IServiceExtended,并使用组合。但是我不认为它会解决任何问题然后它会产生,这完全取决于你想在兔子洞里走多远
  • 配置不应该是服务的属性吗?还是更像参数?如果它可以在服务中,那么您将能够拥有包含具有 int 参数的方法的 IService,以及将提供专用类型的配置的 IService。然后你的服务必须实现两者(可能是一个通用接口)。这对你有用吗?

标签: c# oop interface


【解决方案1】:

Calculate 方法签名需要 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;
    }
}

这不是您确切问题的真正答案:

有没有更好的方法来编写这段代码?

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

【讨论】:

  • 这是我在 SO 上提出这个问题之前尝试过的想法之一。不过,感谢您很好地介绍它,可能对其他人有用。就我个人而言,我不是泛型的忠实拥护者,通常当你让一件事变得通用时,突然间许多其他的东西也必须变得通用。
  • 赞成,似乎是一种理智、直接和现代的方法
  • @Loreno “通常当你让一件事变得通用时,突然间许多其他的东西也必须变得通用。”我知道这样的情况。另一种方法可能是策略模式。 (我想这就是 [DeveloperExceptionError 14 ] 所说的)但我真的不知道它是否适合您的服务构造
  • @Loreno 在服务之间使用Configuration 有何不同?你只在Calculation 中使用它吗?还是其他方法?
  • @Loreno 实际上没有泛型,您总是会感到模棱两可,可以插入普通的Configuration。这通常使用多态性来解决。我认为为此,您需要在Configuration 中使用一个可覆盖的方法,该方法将在ExtendedConfiguration 中被覆盖,并且它的行为必须根据配置类型而有所不同。然后您可以将其用作服务内部和计算方法内部的属性
【解决方案2】:

我想这归结为“更好”的定义。我个人不喜欢对 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 中提取设置,或者您选择存储它们。

【讨论】:

  • 您的解决方案很复杂。可能非常好,并且遵循了一些好的实践,但是我个人在看的时候并不能快速理解这样的代码。
  • @Loreno,如果您发现我的解决方案很复杂,那么也许我解释得不够好。你很难理解哪一部分?
  • 不,这根本不是你的错。我只是觉得很难在这样的抽象中思考,我真的应该改进自己。
【解决方案3】:

我不觉得这个解决方案很糟糕;试图统一处理继承层次结构中的对象的算法有一个缺点,即在某些时候您需要在运行时设置或检查对象的某些特征。原因正是您所经历的:如果您遇到边缘情况,很少有一种解决方案适合所有情况。

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

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

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

【讨论】:

    【解决方案4】:

    其实没有问题。如前所述,铸造失败。

    您不想使用泛型类型方法。也许你可以创建一个 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;
        }
    }
    

    【讨论】:

    • 我认为您误解了这个问题。 ExtendedConfiguration 比 Configuration 有更多的数据。如果有人在计算方法中使用了配置,该方法将失败 - 它需要扩展配置,因为它包含所需的数据(字符串过滤器)
    猜你喜欢
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-14
    相关资源
    最近更新 更多