我在想处理这种情况的正确方法是什么:我有一个接口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-否则它将无法进行计算(并会引发异常)。
有没有更好的方式编写此代码?
答案 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;
}
}