views:

855

answers:

6

Hi

When reviewing our codebase, I found an inheritance structure that resembles the following pattern:

interface IBase
{
    void Method1();
    void Method2();
}

interface IInterface2 : IBase
{
    void Method3();
}

class Class1 : IInterface2
{
    ...
}

class Class2 : IInterface2
{
    ...
}

class Class3 : IInterface2
{ 
    ...
}

In Class2, Method1 throws NotImplementedException.

Questions:

  • What do you think in general about inheriting interfaces?
  • Does the relationship between IBase and Class2 violate the Liskov Substitution Principle?
+7  A: 

Well, first of all, I'm generally against implementing an interface by throwing NotImplementedException exceptions. It is basically like saying "Well, this class can also function as a calculator, err, almost".

But in some cases it really is the only way to do something "the right way", so I'm not 100% against it.

Just something to be aware of.

An interface is a contract, by implementing the interface you say that you abide by the contract. If you then start negating parts of the contract, it sounds to me as the contract, or the implementation of it, was poorly thought out.


Edit: After seing Greg Beech's answer: If an interface specifically says that implementations should throw these exceptions, then it's part of the contract, then I agree that the class is fully allowed to do it.


As for the substitution principle, it states that:

Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.

In this context, it violates the principle just as much as overriding a method from a base class does, if you change what the method does in the descendant type.

The principle is more detailed on the wikipedia page, like the following points (parenthesis and emphasis on my comments):

  • Preconditions cannot be strengthened in a subclass. (A precondition could be that "the class is ready for a call to this method at this point")
  • Postconditions cannot be weakened in a subclass. (A postcondition could be that after calling the method, something is true about the state of the class)

Since you have not shown the full contract of your interfaces, only the declarative part that the compiler can check, it is impossible to know wether the principle holds for your implementation of not.

For instance, if your Method2 has the following conditions attached to it:

  • Can be called at all times
  • Modifies the state of the object to be ready for the next event in a chain of events

Then throwing a NotImplementedException violates the principles.

However, if the contract also states that:

  • For classes that doesn't support chains of events, this method should throw NotImplementedException or NotSupportedException

Then it probably does not.

Lasse V. Karlsen
+3  A: 

So, I'm assuming the question you're asking is:

If a derived type throws a NotImplementedException for a method where a base type does not, does this violate the Liskov subsitution principle.

I'd say that depends on whether the interface documentation says that a method may throw this exception to fulfill its contract. If so then it does not violate the principle, otherwise it does.

The classic example of thisin the .NET framework is the Stream class which has a bunch of operations such as Read, Write and Seek yet there is no requirement for a stream to support all of these operations, and are documented as being allowed to throw NotSupportedException.

Greg Beech
A: 

Assuming I understand what you mean, then I think the answer is yes, inheriting interfaces is fine, and no, it does not violate the Liskov Substitution Principle.

The way to think about interfaces is as a "behaves like a" operator. It describes a set of behaviours which the class promises to obey, expressed by methods etc. So there is no problem having an IBehavesLikeACat interface inheriting from an IEatsMice interface: if it behaves like a cat then it clearly eats mice. So a Cat would implement both, a ferret only IEatsMice.

Joel in Gö
A: 

Inheritance in interfaces is used several places in the .Net framework. So while not everything in there there is perfect I suppose this is seen as ok. Look at the IEnumerable interface for example.

As to throwing NotImplementedExceptions I would say it depends on how you use them in your code. You can for example define an interface with several methods where some are optional. In that case you should check if the method was available and only use it if it was.

Rune Grimstad
A: 

First of all, inheritance in interfaces is OK, it applies in the same way as it does for class inheritance and is a very powerful tool.

Interfaces describe behavior, let's say an interface defines what a class "can do" so if you implement an interface you are declaring you can do what that interface specifies. For example:

interface ISubmergible
{
  void Submerge();
}

So is obvious, if the class implements the interface then it can submerge. Some interfaces nonetheless imply other interfaces, for example, imagine this interface

interface IRunner
{
  void Run();
}

That defines an interface that indicates that the class implementing it can run... nonetheless, in the context of our program is understood that, if something can run it can obviously walk, so you want to make sure that is fulfill:

interface IWalker
{
  void Walk();
}

interface IRunner : IWalker
{
  void Run();
}

Finally, about the whole NotImplementedException thing... I'm amazed with some of the suggestions. A NotImplementedException should never, ever, be raised by an interface method of a class. If you implement an interface your are explicitly accepting the contract that interface establishes, if you raise a NotImplementedException you are basically saying "ok, I lied, I told you I supported the interface but I really don't".

Interfaces are meant to avoid having to worry about what the class implements and what doesn't. If you take that away they are useless. Much more important, your fellows will expect such behavior, even if you understand what you're doing the rest of your team won't and even you, 6 months from now won't understand the logic beneath it. So class2 violates common sense and interface purpose. Class2 does not implement the IBase so don't claim it does.

Jorge Córdoba
A: 

Throwing NotSupportedException is OK: this is the exception to throw when some interface functionality is not implemented by design.

It's less clear for NotImplementedException: this is normally used for code which is not yet implemented, but will be. For example, stubs for interface implementations generated by Visual Studio 2008 contain the code "throw new NotImplementedException()".

See Brad Abram's blog for a discussion of this.

Joe