假设以下代码:
namespace Example {
public interface IBase {
string CommonMember { get; set; }
}
public interface IDerived : IBase {
void DoSomething();
}
public interface IComplexDerived : IBase {
IEnumerable<object> Junk { get; }
}
}
我目前正在进行的项目中有类似的结构。接口IBase
主要用于将IDerived
和IComplexDerived
的实例保存在同一容器中(如List<IBase>
)并且不必重复公共接口成员定义(例如示例中的CommonMember
)。
这将使用的一种方式是这样的:
public class Foo {
public void Bar( IEnumerable<IBase> instances ) {
foreach( IBase instance in instances ) {
if( instance is IDerived ) { /* do something */ }
else if( instance is IComplexDerived ) { /* do something else */ }
}
}
}
因此,没有什么能阻止用户实现IBase
并将该类的实例传递到系统中。但这样做完全没用,因为整个库只需要处理实现IBase
派生接口的类。
这个概念当然是完整记录的,不应该引起任何问题。但是,我想知道是否可以通过语言本身来传达这一点。就像有一个抽象类,但对于接口。
您可能会问为什么不简单地使用抽象类。原因是我们不想强制要求从我们的班级继承。
答案 0 :(得分:2)
我不确定这在你的实际案例中是否可行,但我认为你可以
IComplexDerived
继承自IDerived
而不是IBase
。IDerived
而不是IBase
的列表,所以即使IBase
的新实现也不会进行类型检查(因为您需要IEnumerable<IDerived>
) IComplexDerived
的类只会以不同的方式实现DoSomething()
。通过执行此操作,您可以让Bar
方法以多态方式决定需要调用的DoSomething(并避免检查类型)我的意思是这样的:
public interface IBase {
string CommonMember { get; set; }
}
public interface IDerived : IBase {
void DoSomething();
}
//IComplexDerived isnow a IDerived
public interface IComplexDerived : IDerived {
IEnumerable<object> Junk { get; }
}
public class Foo
{
// Bar requires IEnumerable<IDerived> so you can't call it with a collection
// of classes implementing IBase
public void Bar( IEnumerable<IDerived> instances ) {
foreach( IDerived instance in instances ) {
instance.DoSomething(); // DoSomething will "do something else" in
// classes implementing IComplexDerived
}
}
}
答案 1 :(得分:0)
一种可能性是从IDerived
和IComplexDervied
中删除公共接口,并创建一个包装类,它接受其中一个的实例并提供通用功能:
public interface IDerived
{
void DoSomething();
string CommonMember { get; set; }
}
public interface IComplexDerived
{
IEnumerable<object> Junk { get; }
string CommonMember { get; set; }
}
public class EitherDerived : IBase
{
private readonly IDerived derived;
private readonly IComplexDerived complex;
private readonly bool isComplex;
public EitherDerived(IDerived derived)
{
this.derived = derived;
this.isComplex = false;
}
public EitherDerived(IComplexDerived complex)
{
this.complext = complex;
this.isComplex = true;
}
public string CommonMember
{
get
{
return isComplex ? complex.CommonMember : derived.CommonMember;
}
set
{
//...
}
}
public TOut Either<TOut>(Func<IDerived, TOut> mapDerived, Func<IComplexDerived, TOut> mapComplex)
{
return isComplex ? mapComplex(complex) : mapDerived(derived);
}
}
如果你想确定你正在处理其中一个类,那么你可以使用这个类而不是你的IBase
接口:
private object HandleDerived(IDerived derived) { ... }
private object HandleComplex(IComplexDerived complex) { ... }
public void Bar(IEnumerable<EitherDerived> instances)
{
foreach(var either in instances)
{
object _ = either.SelectEither(HandleDerived, HandleComplex);
}
}
答案 2 :(得分:0)
完全寻找不同设计的建议就是我最终要做的事情。由于这是一个开源项目,我们可以查看实际结果。
IBase
为ITimelineTrackBase
,描述了所有派生类型共有的界面成员。
IDerived
是ITimelineTrack
,它描述了时间轴上的曲目,该曲目由包含开头和结尾的单个元素组成。
IComplexDerived
为IMultiPartTimelineTrack
,它描述了时间轴上的曲目,该曲目由多个元素组成,每个元素都有一个开头和一个结尾。
与我之前的计划相反,我不是将它们存储在List<IBase>
中,而是使用List<IComplexDerived>
。或者,就应用而言,List<IMultiPartTimelineTrack>
。
现在我决定不接受IBase
任何地方,如果那不是我真正想要支持的方法。因此ITimelineTrackBase
纯粹用作基本接口,并且不作为库中任何位置的可接受参数类型提供。
相反,整个库处理单轨道元素(ITimelineTrack
)或其集合(IMultiPartTimelineTrack
)。根据需要,前者通过辅助构造SingleTrackToMultiTrackWrapper
包裹在后者中。
因此,我没有让它无法实现界面,而是让它实现它毫无意义。