Não há como exigir que uma implementação lance uma exceção por meio de uma interface, mesmo em linguagens como Java, onde você pode declarar que um método pode lançar uma exceção.
Pode haver uma maneira de garantir (até certo ponto, mas não absolutamente) que uma exceção seja lançada. Você pode criar uma implementação abstrata da sua interface. Em seguida, você pode implementar o GetUser
método como final na classe abstrata e usar o padrão de estratégia para chamar outro membro protegido da subclasse e lançar uma exceção se retornar algo diferente de um usuário válido (como nulo). Isso ainda pode cair se, por exemplo, o outro desenvolvedor retornar um tipo de objeto nulo User
, mas eles realmente precisariam trabalhar para subverter a intenção aqui. Eles também podem apenas reimplementar sua interface, também são ruins, portanto, você pode considerar substituir a interface inteiramente pela classe abstrata.
(Resultados semelhantes podem ser alcançados usando delegação em vez de subclassificar com algo como um decorador de embalagens.)
Outra opção poderia ser criar um conjunto de testes de conformidade que todo código de implementação deve passar para ser incluído. A eficácia disso depende de quanto controle você tem sobre a vinculação do outro código ao seu.
Também concordo com outras pessoas que a documentação e a comunicação claras são fornecidas quando um requisito como esse é esperado, mas não pode ser completamente imposto no código.
Exemplos de código:
Método da subclasse:
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
}
Método decorador:
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
}
Um último ponto rápido que quero destacar que esqueci antes é que, ao fazer qualquer uma dessas opções, você está se comprometendo a uma implementação mais específica, o que significa que os desenvolvedores da implementação têm muito menos liberdade.