[英]Can you model an interface where only it's descendants can be implemented?
假設以下代碼:
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
實例IComplexDerived
在同一容器中(如List<IBase>
),也不必重復公共接口成員定義(如CommonMember
中的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
派生的接口的類。
這個概念當然是完整記錄的,不應該引起任何問題。 但是,我想知道是否可以通過語言本身來傳達這一點。 就像有一個抽象類,但對於接口。
您可能會問為什么不簡單地使用抽象類。 原因是我們不想強制要求從我們的類繼承。
我不確定這在你的實際案例中是否可行,但我認為你可以
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
}
}
}
一種可能性是從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);
}
}
完全尋找不同設計的建議就是我最終做的事情。 由於這是一個開源項目,我們可以查看實際結果。
IBase
是ITimelineTrackBase
,描述了所有派生類型共有的接口成員。
IDerived
是ITimelineTrack
,它描述了時間軸上的軌道,該軌道由具有開始和結束的單個元素組成。
IComplexDerived
是IMultiPartTimelineTrack
,它描述了時間軸上的軌道,該軌道由多個元素組成,每個元素都有一個開頭和一個結尾。
與我之前的計划相反,我不是將它們存儲在List<IBase>
,而是使用List<IComplexDerived>
。 或者,就應用而言, List<IMultiPartTimelineTrack>
。
現在我決定不接受任何地方的IBase
,如果那不是我真正想要支持的方法。 因此, ITimelineTrackBase
純粹用作基本接口,並且不作為庫中任何位置的可接受參數類型提供。
相反,整個庫處理單軌道元素( ITimelineTrack
)或其集合( IMultiPartTimelineTrack
)。 根據需要,前者通過輔助構造SingleTrackToMultiTrackWrapper
包裝到后者中。
因此,我沒有讓它無法實現接口,而是讓它實現它毫無意義。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.