Assume the following code:
namespace Example {
public interface IBase {
string CommonMember { get; set; }
}
public interface IDerived : IBase {
void DoSomething();
}
public interface IComplexDerived : IBase {
IEnumerable<object> Junk { get; }
}
}
I have a similar structure in the project I'm currently working on. The interface IBase
primarily serves the purpose to be able to keep instances of IDerived
and IComplexDerived
in the same container (like a List<IBase>
) and also not having to repeat common interface member definitions (like CommonMember
in the example).
One way this would then be used would be something like this:
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 */ }
}
}
}
So, nothing would stop the user from implementing IBase
and passing instances of that class into the system. But doing that would be completely useless because the whole library only expects to deal with classes that implement interfaces that were derived from IBase
.
This concept is of course fully documented and shouldn't cause any problems. However, I was wondering if it would be possible to communicate this through means of the language itself. Like having an abstract class, but for interfaces.
You might ask why not simply use an abstract class then. The reason for that is that we don't want to impose the requirement to inherit from our class.
I'm not sure if this is feasible in your actual case, but I think you could have
IComplexDerived
inherit from IDerived
instead of IBase
. IDerived
instead of IBase
, so even a new implementation of IBase
would not type-check (since you require an IEnumerable<IDerived>
) IComplexDerived
would simply implement DoSomething()
in a different way. By doing this you would let your Bar
method decide polymorphically what DoSomething it needs to call (and avoid checking on the type) I mean something like this:
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
}
}
}
One possibility is to remove the common interface from IDerived
and IComplexDervied
and create a wrapper class which takes an instance of one of them and provides the common functionality:
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);
}
}
Then you can use this class instead of your IBase
interface if you want to be sure you are dealing with one of those classes:
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);
}
}
The suggestion to look for a different design altogether was what I ended up doing. Since this is an open source project, we can look at the actual results.
IBase
is ITimelineTrackBase
and describes interface members that are common to all derived types.
IDerived
is ITimelineTrack
and it describes a track on a timeline which consists of a single element with a start and end.
IComplexDerived
is IMultiPartTimelineTrack
and it describes a track on a timeline which consists of multiple elements that each have a start and an end.
Contrary to my earlier plans, I'm not storing these in a List<IBase>
, but I'm using List<IComplexDerived>
. Or, in terms of the application, a List<IMultiPartTimelineTrack>
.
Now I decided to not accept an IBase
anywhere if that's not what I actually want to support in that method. So the ITimelineTrackBase
is used purely as a base interface and isn't offered as an accepted parameter type anywhere in the library.
Instead the whole library deals either with single track elements ( ITimelineTrack
) or a collection of those ( IMultiPartTimelineTrack
). As needed, the former is wrapped into the latter by a helper construct SingleTrackToMultiTrackWrapper
.
So instead of making it impossible to implement the interface, I just made it pointless to implement it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.