1

I have an interface -though it could also be an abstract class- that should allow its implementers to specify whether they support a particular feature and I'm looking for the best way to achieve that.

There are several choices here:

Looking at BCL, I see that the Stream class has properties like CanRead and CanSeek, which can be used to check if a feature is supported and throws a NotSupportedException, if I do something that is not supported.

public interface IHandler
{
    bool SupportsA { get; }
    bool SupportsB { get; }

    void A();
    void B();
}

This would be an appropriate approach for me, though I have more than a few features that may or may not be supported and I prefer not to define a "SupportsX" property for each one.

I'm also very fond of bitwise Enum values and I think that another way would be:

[Flags]
public enum HandlerFeature
{
    None = 0
    A = 1,
    B = 2,
    C = 4
}

public interface IHandler
{
    HandlerFeature Features { get; }

    void A();
    void B();
    void C();
}

And an extension method like this could be written:
bool Supports<T>(this T handler, HandlerFeature feature) where T : IHandler

Which I think would also be much prettier than the other approach but, I couldn't help but think that if every Enum value would correspond a member in the contract, I should be able to mark those members more explicitly. Then I thought about attributes:

public interface IHandler
{
    [HandlerRequires(Feature.None)]
    HandlerFeature Features { get; }

    [HandlerRequires(Feature.A)]
    void A();

    [HandlerRequires(Feature.B)]
    void B();

    [HandlerRequires(Feature.A | Feature.B)]
    void AB();

    [HandlerRequires(Feature.C)]
    void C();
}

Although I don't know how to leverage an attribute like this in runtime, it definitely makes the interface definition looks explicit enough.

Is there a best-practice for this?
Or another approach you recommend?
Or anything wrong with the ones I specified?

Şafak Gür
  • 7,045
  • 5
  • 59
  • 96

1 Answers1

1

If you have to build an interface that must use all this stuff, I recommend that you do it with Booleans because the developer has to specify a value for that property when he or she is implementing that interface. Using a flagged enum or attributes can result in classes where e.g. method A is not implemented but the developer simply forgot to set the Attribute or enum correctly. In this case, the compiler would not emit an error.

Anyway, I would advise you to not construct such a "fat" interface. I would encourage you to read about the "Interface Segregation Principle" which is part of the SOLID principles of object-oriented design (Google it, you'll find several articles). It states that clients should not be forced to depend on methods they do not use. The consequence of this is that you use multiple small interfaces instead of one that clasps several aspects. Now I don't know the context your working in but if your interface integrates several cross-cutting concerns like e.g. logging besides its basic business capabilities, I would strongly recommend to throw out logging of the interface and use the Decorator pattern instead (also, Google this if you don't know it :-) ).

Hope this helps.

feO2x
  • 5,358
  • 2
  • 37
  • 46
  • 1
    +1 and yes, that also came with a comment from Ani. You are right and I completely agree but, in my scenario it really isn't *that fat* :) And about the attribute, it was never my intention to make the implementers use it, I hoped to get away with using it only in the contract, somehow. And thank you for the concepts you mentioned. – Şafak Gür Apr 26 '13 at 16:47
  • In cases where code which receives something of one interface type would often want to use a feature of another if present, it may be advantageous to have the former interface include the methods associated with the feature. For example, having `IEnumerable` include properties to indicate whether something is bounded, quickly countable, or neither, along with a `Count()` method (which could, if nothing else, either call `Enumerable.CountByEnumeration(this)` (if bounded) or throw `NotSupportedException` (if unbounded) would be cleaner than having to try-cast to `ICollection`. – supercat Jan 16 '14 at 20:15
  • @supercat: I disagree. If I want an enumerable that has a finite number of items, I would use the IReadOnlyCollection or IReadOnlyList interfaces instead of IEnumerable. Otherwise I would violate the Liskov Substitution Principle. Mark Seemann wrote an interesting blog entry about this: http://blog.ploeh.dk/2013/07/20/linq-versus-the-lsp/ – feO2x Jan 17 '14 at 14:38
  • @feO2x: Those interfaces substantially post-date `IEnumerable`. Further, in cases where an object may or may not know certain things about itself, I fail to see an LSP violation in providing methods to ask it, *provided the contract of those methods allows for an "I don't know" response*. Further, if one segregated all the different pieces of `IList` into different interfaces, how could one avoid having to write different wrapper classes to deal with every different interfaces? If there's a unified means of asking what abilities exist, a wrapper can pass that through. – supercat Jan 17 '14 at 15:37
  • @feO2x: Finally, if some *instances* of a class might be able to make promises or offer features that other instances could not, I don't see any way to allow clients to take advantages of those promises or features without being able to ask whether they are supported. For example, if an object returns an enumeration based upon an arbitrary query, the fact that it may not be able to guarantee that it can always return a count faster than it can enumerate the items doesn't mean it shouldn't offer to return a quick count when it can do. – supercat Jan 17 '14 at 15:43
  • @supercat: You are right, `IReadOnlyCollection` and `IReadOnlyList` were introduced in .NET 4.5 as far as I remember. But they are available now, so why shouldn't we use them? `IEnumerable` just states that there is an enumerator that is able to move next and that might stop, but the latter behavior is not ensured (in that case, we'd have an infinite number of items). The `IReadOnlyCollection` interface solves this with its `Count` property: this enumerable must be able to tell you how many items are referenced by it at any time (always a finite number). – feO2x Jan 21 '14 at 09:41
  • @supercat: Furthermore IMHO, I would not advise to include members in interfaces that allow the client to ask about certain capabilities of the target object. This usually results in more logic at the client side because you have to check the target object via the interface and result with different code according to the outcome of this checks. What the client normally wants is to reference other objects and just tell them to do something. The principle behind this is Tell Don't Ask. Martin Fowler wrote an article about that on his website: http://martinfowler.com/bliki/TellDontAsk.html – feO2x Jan 21 '14 at 09:50
  • @supercat: In my opinion, interface members that allow to ask capabilities of the target object violate the Interface Segregation Principle which states that an interface should be tailored for the client, not for the class that implements that interface. In the end, this results in more interfaces but these interfaces are also smaller and therefore can be used in an easier way by the client. In my projects, I usually have several dozens of interfaces and most of them just contain a single method. – feO2x Jan 21 '14 at 09:55
  • @feO2x: There are many things which can be done, awkwardly, with the vast majority of `IEnumerable` implementations, using nothing but the members of that interface. For example, if one wants the 45,762nd through 45,801st item, one could receive them by calling `GetEnumerator` and--with the returned object--skipping 45,761 items, reading the next ten, and calling `Dispose`. For some implementations, that approach would be the best. For others, it would be orders of magnitude slower than the best approach. The "tell don't ask" philosophy would dictate... – supercat Jan 21 '14 at 16:06
  • ...that rather than having clients ask "can you do operation XX quickly", clients should specify their goal and let the object figure out how best to do it, but going too far in that direction can really bloat interfaces. Still, I would posit that a collection interface should provide means by which operations like "copy a range of items from one collection to another" could be performed on array-backed collections using one or more bulk copy operations rather than item-by-item enumeration, and also provide "give me an object that will always encapsulate your present sequence of items". – supercat Jan 21 '14 at 16:16
  • @feO2x: As for the Interface Segregation Principle, if you list all the different combinations of features in `IList` that an object might be able to do "easily", there are probably over a dozen. Likewise if you list all the combinations of features that clients might need. If the .NET framework made it possible to declare something as e.g. `{IReadableByIndex, ICountable, IPermutableByIndex}`, then segregating out all the distinct abilities might reasonable, but absent such a feature, having a composite interface with an ability to find out *in detail* want an object can do... – supercat Jan 21 '14 at 16:23
  • @feO2x: ...would seem the best reasonable alternative. Also, if `IList` were split up into independent interfaces, could a single class like `ReadOnlyCollection` wrap things that included different combinations of features and, for each, expose all the "read-related" abilities (while hiding any mutating methods), or would one need a different class for every combination of abilities? – supercat Jan 21 '14 at 16:29