Unintended method exposure using inheritance with a generics typed class
标题我已经尽力了。我想要完成的是带有依赖注入的分层模块化。这种设计模式是否好是另一个论坛的问题。
因为我使用依赖注入,所以我有接口/实现对。这是顶级接口:
1 2 3 4
| public interface IConfiguration< T > where T : ConfigData
{
T GetConfig();
} |
其中 ConfigData 是一个简单的类,它公开 get/set 属性,如 LogLevel 和 Environment。
接口有一个基本实现:
1 2 3 4 5 6 7 8 9
| public abstract class ConfigurationBase< T > : IConfiguration
{
protected ConfigData Config { get; set; }
public T GetConfig()
{
return Config as T;
}
} |
现在是依赖注入部分!我有几个分层继承的接口/实现对。此外,它们的 protected Config 属性还在每个后续子类中公开了更多属性。这是我的接口/实现签名:
1 2 3 4 5 6 7 8
| public interface IGeneralConfiguration : IConfiguration<GeneralConfigData>
public class GeneralConfiguration : ConfigurationBase<GeneralConfigData>, IGeneralConfiguration
public interface ILoginConfiguration : IConfiguration<LoginConfigData>, IGeneralConfiguration
public class LoginConfiguration : ConfigurationBase<LoginConfigData>, ILoginConfiguration
public interface IAppConfiguration : IConfiguration<AppConfigData>, ILoginConfiguration
public class AppConfiguration : ConfigurationBase<AppConfigData>, IAppConfiguration |
请注意,配置数据元素的继承方案是 ConfigData → GeneralConfigData → LoginConfigData → AppConfigData。配置数据元素只是在每个子项中公开更多特定于登录/应用程序等的属性(如 Username 或 StartUri)。
现在,我可以在我的所有模块中使用这个配置概念。就依赖注入而言,解析 IGeneralConfiguration、ILoginConfiguration 或 IAppConfiguration 将产生完全相同的实例。但是,现在通用模块只需要解析IGeneralConfiguration,特定于登录的模块只需要解析ILoginConfiguration,而特定于应用程序的模块可以解析IAppConfiugration,所有这些都可以访问其特定的部分配置数据他们试图处理的问题。这种模块化允许我创建更小的侧应用程序,这些应用程序可以重用来自主应用程序的模块,而无需进行大量自定义编码(例如,我可以重用登录模块而无需引用特定于应用程序的模块),只要我稍微改变我的依赖注册。
如果你到现在为止还和我在一起,这个模型的唯一问题是在我所有的子类中(继承自 ConfigurationBase<T>),它们都需要来自它们上面的接口的 ConfigData() 实现.这意味着 class LoginConfiguration 需要 public GeneralConfigData GetConfig() 的方法定义,class AppConfiguration 需要 public GeneralConfigData GetConfig() 和 LoginConfigData GetConfig() 的方法定义。
很好。我这样做。现在,在我的应用程序特定模块中,出现编译器错误。在我的类字段定义中,我有 private IAppConfiguration _appConfiguration;。稍后在一个方法中,我引用了它:
1
| var element = _appConfiguration.GetConfig().AppSpecificConfigElement; |
编译器很困惑,说
the call is ambiguous between the following or properties 'IConfiguration.GetConfig()' and 'IConfiguration.GetConfig()'
为什么编译器没有看到类型是 IAppConfiguration 并定义对 AppConfiguration 的 GetConfig() 的调用(其中 T 定义为 AppConfigData)?
有没有一种明显的方法可以使用我的方案来消除对 GetConfig() 的调用的歧义?
- 试试这个:var element = ((IConfiguration<AppConfigData>)_appConfiguration).GetConfig??().AppSpecificConfig??Element;
-
另外,作为旁注,您刚刚所做的事情将使追随您的人的生活变得非常痛苦。配置对象应该是 POCO,或者是具有简单界面的最大 POCO(即使只有当您坚持使用经典 .net 和类似桌面的东西时)。这允许通过 IoC 框架从任何源(db、文本文件、json、外部源)根据需要注入它们。如果您需要继承,您只需拥有 Config1 : Config2 和 Config2 : Config3 链,并将相同的实例传递给 IoC。
-
我没有得到抽象 ConfigurationBase<T> 类的这一部分:protected ConfigData Config { get; set; } 如果有的话,它应该是 protected T Config { get; set; }。
-
@zaitsman 问题的复杂性中丢失了一些细节。实现的 IConfiguration 继承链有两个目的。 (1) 在该层公开 Config POCO 和 (2) 公开特定于该层的配置方法,例如SaveLoginConfig() 代表 ILoginConfiguration 和 LoadUserProfile() 代表 IAppConfiguration。我没有将其包含在问题中,因为我担心它会混淆细节,但知道我为什么这样做肯定会有所帮助。
-
另外,@zaitsman,您的建议在语法上完全有效,但是强制转换违背了所有这些复杂性的目的。看起来您通常认为这种复杂性弊大于利(您可能完全正确)。我正在考虑将这个问题放在软件工程 SE 或 CodeReview SE 上以获得人们的意见。
-
@ZoharPeled 看到上面的两条评论 - 关键是每一层的复杂性(一般与登录与应用程序)都会(1)公开特定于该层的配置对象和(2)公开特定于该层的类似配置的方法. Config 受到保护,因为我不希望它被公开设置,但我确实希望子类可以访问它。 public T GetConfig() 旨在提供便利,因此如果我知道我正在使用登录或应用程序层,我将自动获得对已转换为适当类型的 ConfigData 元素的引用。
-
@MichaelPlautz 查看 explicit interface implementation。那或类型转换确实是您唯一的选择。
如果我理解正确,那么您刚才所做的是,除了无法自动解析的返回值之外,您有两个具有相同签名的方法。编译器不会(也不能)遍历从 ConfigData 派生的所有子类以确定 AppSpecificConfigElement 属于 AppConfiguration 并基于此选择重载 - 即使这样做,您也可以拥有多个具有 AppSpecificConfigElement 属性的类所以它不会更明智。您需要帮助编译器了解您的需求,方法是键入 _appConfiguration 到正确的类型,或者首先在语句中使用 ConfigData 的类型化后代而不是 var 然后获取属性。
在这两种情况下,我认为您严重过度设计,我建议您退后一步,重新考虑您的方法。正如@zaitsman 所说,这些对象应该是 POCO,并且具有不同的加载器(数据库、文件系统,...),实现简单的 Load/Save 接口,然后可以根据上下文传递给 DI。
- 您指出我想要的东西是不可能的。上网问,X 能做 Y 吗?是一回事,但要自信地说 X 不能做 Y,需要更多的知识和研究。