【发布时间】:2010-12-06 17:00:20
【问题描述】:
在下面的层次结构中,有没有办法在编译时排除 IBars 同时仍然允许 IFoos?
例子:
//IFoo defines functionality that Snafu needs to use, and so is a type restriction.
public interface IFoo {...}
//IBar defines supplemental required functionality that Snafu does not support.
public interface IBar:IFoo {...}
//Can I generate a compiler error if an IBar is used as the generic type?
public class Snafu<T> where T:IFoo
{
public void DoSomethingWith(T myFoo)
{
//Best thing I can think of with the current hierarchy
if(myFoo is IBar) throw new ArgumentException("IBars are not supported");
}
}
我能想到的唯一可行的方法是定义第三个“标志”接口,并将其应用于所有不是 IBar 的 IFoo。定义了 IFoo 和 IBar:
public interface IAmNotAnIBar:IFoo {}
public class Snafu<T> where T:IAmNotAnIBar {...}
但是,这闻起来像 hack;新接口没有定义任何 IFoo 没有定义的新接口,开发人员必须知道不要使用 IFoo(IIRC 你不能通过将 IFoo 设为内部来隐藏它,同时仍将其他接口公开),所以他们可以只需实现 IFoo,就可以善意或恶意地绕过编译时检查。还有什么更优雅的吗?
编辑:感谢所有到目前为止的回复。也许一个更具体的例子可以说明我为什么首先提出这个问题。
假设 IFoo 定义了映射到 DB 的域对象的基本功能。它必须具有读写 ID 属性,也许能够发射和吸收 DTO,等等。系统中有几个区域将这些对象作为 IFoo 处理,包括持久性: IFoo 使用可以处理任何 IFoo 的存储库(上例中的类 Snafu;DoSomething() 执行一些持久性操作)被读取和写入持久性存储在强类型 IFoo 上)。
现在,让我们将 IBar 定义为与 IFoos 不同的域对象。除了 IFoo 功能之外,它们还定义了一些额外的 Initialize() 方法,以确保它们处于一致的状态。它们仍然是域对象 (IFoos),并且在系统的所有区域中仍应如此对待,除非您不能将 IBar 传递给 Snafu 的实例并期望得到正确的结果,因为 Snafu 不会也不能正确调用该方法(假设 Initialize() 接受一个参数,该参数是 Snafu 没有也不应该给出的外部依赖项)。相反,您应该调用一个不同的类,它是所有 IBar 的存储库。我的目标是在编译时提醒开发人员他们做错了什么,而不是依赖于对 Snafu 上的每个可能的 DoSomething() 调用进行充分的运行时测试(单元、集成、功能、手动等)以确保它永远不会通过 IBar。
【问题讨论】:
-
IFoo是 only 允许的类型吗?或者是否允许IBar以外的后代? -
如果 IBar 扩展 IFoo,它应该做 IFoo 所做的所有事情,以及其他事情。如果 IBar 不能用于需要 IFoo 的地方,则 IBar 不应该真正扩展 IFoo。 Liskov substitution principle
-
@dtb:我同意第一和第二部分,但第一部分并不一定意味着第二部分。见编辑; IBar 完成了 IFoo 所做的一切,但也公开了必须在特定情况下使用的附加功能。因此,在这种情况下,IBar 不能用作 IFoo(是的,这违反了 LSP)。
-
根据您的编辑,我会说 IFoo 和 IBar 是不同的东西,尽管它们共享一些功能并且可以与某些操作一起使用。所以 IFoo 和 IBar 都应该扩展 IFooBarBase,这样在两者都可以接受的情况下,两者都可以使用,而当操作仅对此有意义时,只能使用两者中的一个。
-
你所谓的“标志接口”实际上可以是一个非常好的模式。考虑接口
IReadableMatrix、IImmutableMatrix和IChangeableMatrix。IImmutableMatrix不能“做”任何IReadableMatrix不能做的事情,但可以做出前者做不到的承诺。请注意,虽然有些人可能会争辩ImmutableMatrix应该是一个类,但如果愿意信任实现者,将其作为接口是有好处的。除此之外,如果未来的应用程序需要存储内容符合某些以前无法预料的模式的不可变矩阵,则有可能......