Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Suppose you had the following interface

public interface IUserRepository
{
    User GetByID(int userID);
}

How would you enforce implementers of this interface to throw an exception if a user is not found?

I suspect it's not possible to do with code alone, so how would you enforce implementers to implement the intended behavior? Be it through code, documentation, etc.?

In this example, the concrete implementation is expected to throw a UserNotFoundException

public class SomeClass
{
    private readonly IUserRepository _userRepository;

    public SomeClass(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void DisplayUser()
    {
        try 
        {
            var user = _userRepository.GetByID(5);
            MessageBox.Show("Hello " + user.Name);
        }
        catch (UserNotFoundException)
        {
            MessageBox.Show("User not found");
        }
    }
}
share|improve this question
2  
This is really a very interesting question. I have a feeling the correct answer might involve Attributes or an IL hack. Generally, I'd assume that attempting it is a wrong choice, because each implementer should be able to throw their own types of exceptions, but it's still conceivably useful. Maybe you'd just use an abstract class with a template method? –  Magus May 1 at 21:12
10  
Technology doesn't solve people problems. If people violate the interface, hit them with a stick until they stop. –  Telastyn May 1 at 21:16
1  
@Telastyn Maybe I should build a program that beats people with sticks instead. –  Matthew May 1 at 21:17
1  
@Doval: So how does the Interface know whether a valid user exists or not without an implementation backing it? –  Robert Harvey May 1 at 21:20
1  
@delnan Sure, but the interface doesn't distinguish between the two. Those are implementation details. In either case, the user is there or it's not. –  Doval May 1 at 21:50

5 Answers 5

up vote 6 down vote accepted

This is a language feature that was intentionally omitted from C#. Quite simply, it's entirely possible for an IUserRepository.GetByID to fail for some other reason entirely than the user not being found, so you don't want to require a specific error when such cannot happen. You have two options if for whatever reason you want to enforce this behavior:

  1. Define the User class so that it itself throws the exception if it's improperly initalized.
  2. Write unit tests for IUserRepository that explicitly test for this behavior.

Note that neither of those options is "include it in the documentation." Ideally, you should do that anyway, especially as documentation is where you can explain why you want to enforce a particular error type.

share|improve this answer
1  
Even with checked exceptions, you can't force implementers to throw the right exception at the right time. –  svick May 1 at 22:48

There is no way to require an implementation to throw an exception through an interface, even in languages like Java where you can declare that a method could throw an exception.

There may be a way to ensure (to some degree but not absolutely) that an exception is thrown. You can create an abstract implementation of your interface. You can then implement the GetUser method as final in the abstract class, and use the strategy pattern to call another, protected member of the subclass and throw an exception if it returns anything other than a valid user (like null). This can still fall down if, say, the other developer returns a null object type User, but they would really have to work to subvert the intent here. They could also just reimplement your interface, also bad, so you may consider replacing the interface entirely with the abstract class.

(Similar results can be achieved using delegation instead of subclassing with something like a wrapping decorator.)

Another option could be to create a conformance test suite that all implementing code must pass to be included. How effective this is depends on how much control you have over the other code's linking into yours.

I also agree with others that clear documentation and communication are a given when a requirement like this is expected but cannot be completely enforced in code.


Code examples:

Subclass method:

public abstract class ExceptionalUserRepository : IUserRepository
{
    public sealed User GetUser(int user_id)
    {
        User u = FindUserByID(user_id);
        if(u == null)
        {
            throw new UserNotFoundException();
        }
        return u;
    }

    // subclasses implement this method instead
    protected abstract User FindUserByID(int user_id);
    // More code here
}

Decorator method:

public sealed class DecoratedUserRepository : IUserRepository
{
    private readonly IUserRepository _userRepository;

    public DecoratedUserRepository(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public User GetUser(int user_id)
    {
        User u = _userRepository.GetUser(user_id);
        if(u == null)
        {
            throw new UserNotFoundException();
        }
        return u;
    }

    // More code here
}

public class SomeClass
{
    private readonly IUserRepository _userRepository;

    // They now *have* to pass in exactly what you want
    public SomeClass(DecoratedUserRepository userRepository)
    {
        _userRepository = userRepository;
    }
    // More code
}

A last quick point I want to make that I forgot before is that by doing any of these, you are tying yourself to a more specific implementation, meaning implementing developers get that much less freedom.

share|improve this answer

If you work together with the developers that you want to implement the exception-throwing behaviour, then you could possibly write unittests (for them) to check if an exception is thrown if you try to get a user who does not exist.

You therefore need to know which methods to test. I'm not familiar with C# but you could maybe use reflection to search for all classes implementing the IUserRepository and test them automatically so even if a developer adds another class it will be tested when the tests are run.

But that is only what you could do in practice if you really suffer from developers doing the implementations wrong you have to work with. In theory interfaces are not for defining how the business logics has to be implemented.

I'm looking forward for other answers as this is really an interesting question!

share|improve this answer

You can't. That's the thing about interfaces - they allow anyone to implement them at any time. There's an infinite number of potential implementations for your interface, and you can't force any of them to be correct. By choosing an interface, you've forfeited your right to enforce that a particular implementation be used all throughout the system. Heck, what you have isn't even an interface, it's a function. You're writing code that passes functions around.

If you need to go this route then you'll simply have to clearly document the specification that implementations must conform to and trust that no one will deliberately break that specification.

The alternative is to have an abstract data type, which translates into a class in Java/C#-like languages. The problem is that mainstream languages have weak support for ADTs and you can't switch the implementation of the class without file system tomfoolery. But if you've willing to live with that, you can ensure that there's only one implementation in the system. That's a step up from interfaces, where your code may break at any time, and when it does you'll have to figure out which implementation of the interface broke your code and where it came from.

EDIT: Note that even IL hacks won't allow you to ensure the correctness of certain aspects of an implementation. For example, it's implicitly assumed that GetByID must either return a value or throw an exception. Someone could write an implementation that simply goes into an endless loop and does neither. You can't prove during runtime that the code loops forever - if you manage to do this, you've solved the Halting Problem. You might be able to detect trivial cases (e.g. recognize a while (true) {}) but not an arbitrary piece of code.

share|improve this answer
    
Well, an interface does prevent some wrong implementations: Those which are ill-typed (don't match the method signatures in the interface). It can't express the constraint OP wants, but few type systems can that. –  delnan May 1 at 21:25
    
@delnan If a piece of code has a different type, you can hardly call it an implementation! But we're arguing semantics. I get what you mean. –  Doval May 1 at 21:26
    
Another thing: An "abstract data type" is an informal notion much like documentation; to make it machine-checkable you need to construct a type system and use that type system's equivalent of interfaces. –  delnan May 1 at 21:27
    
@delnan That's a misconception. See On Understanding Data Abstraction, Revisited. An ADT abstracts data like an interface, yes. But an ADT does so by obscuring the identity of an existing type. This is equivalent to a class with private fields in mainstream OOP. The obscured type is the record whose fields you've made private. An interface on the other hand is a bundle of functions, and the implementation type appears nowhere in the signatures. This is what allows multiple implementations and substitutability. –  Doval May 1 at 21:29
    
This appears to be a rather different use of "abstract data type" than I'm used to; what I and many others know as abstract data type is just a tool for talking about data types in a language-agnostic manner. What you describe appears to be a (class of) type system feature(s) that interpret and implement the aforementioned notion of abstract data types. –  delnan May 1 at 21:41

There's no way to 100% force the behavior you want, but using code contracts you could say that implementations must not return null, and perhaps that the User object has the id passed in (so an object with userid 0 would fail if 1 was passed). On top of that, documenting what you expect of implementations might help as well.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.