Suponha que você tenha a seguinte interface
public interface IUserRepository
{
User GetByID(int userID);
}
Como você aplicaria aos implementadores dessa interface uma exceção se um usuário não fosse encontrado?
Eu suspeito que não é possível fazer apenas com o código; então, como você aplicaria os implementadores a implementar o comportamento pretendido? Seja através de código, documentação, etc.?
Neste exemplo, espera-se que a implementação concreta lance um 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");
}
}
}
c#
.net
object-oriented-design
interfaces
Mateus
fonte
fonte
Respostas:
Esse é um recurso de idioma que foi intencionalmente omitido do C # . Simplesmente, é perfeitamente possível que uma
IUserRepository.GetByID
falha por algum outro motivo não seja o usuário que não foi encontrado, portanto você não deseja exigir um erro específico quando isso não pode acontecer. Você tem duas opções se, por qualquer motivo, quiser impor esse comportamento:User
classe para que ela mesma gere a exceção se for inadequadamente inicializada.IUserRepository
esse teste explicitamente para esse comportamento.Observe que nenhuma dessas opções é "inclua-a na documentação". Idealmente, você deve fazer isso de qualquer maneira, especialmente porque é na documentação que você pode explicar por que deseja aplicar um tipo de erro específico.
fonte
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 nuloUser
, 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:
Método decorador:
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.
fonte
Se você trabalhar em conjunto com os desenvolvedores que deseja implementar o comportamento de lançamento de exceção, poderá escrever unittests (para eles) para verificar se uma exceção é lançada se tentar obter um usuário que não existe.
Portanto, você precisa saber quais métodos testar. Eu não estou familiarizado com C #, mas talvez você possa usar a reflexão para procurar todas as classes que implementam o IUserRepository e testá-las automaticamente, mesmo que um desenvolvedor adicione outra classe, ela será testada quando os testes forem executados.
Mas isso é apenas o que você poderia fazer na prática se realmente sofrer com desenvolvedores fazendo as implementações erradas com as quais precisa trabalhar. Em teoria, as interfaces não são para definir como a lógica de negócios deve ser implementada.
Estou ansioso por outras respostas, pois essa é realmente uma pergunta interessante!
fonte
Você não pode. Essa é a questão das interfaces - elas permitem que qualquer pessoa as implemente a qualquer momento. Há um número infinito de possíveis implementações para sua interface e você não pode forçar nenhuma delas a estar correta. Ao escolher uma interface, você perdeu o direito de impor que uma implementação específica seja usada em todo o sistema. Caramba, o que você tem nem é uma interface, é uma função. Você está escrevendo um código que transmite funções.
Se você precisar seguir esse caminho, basta documentar claramente a especificação com a qual as implementações devem estar em conformidade e confiar que ninguém quebrará deliberadamente essa especificação.
A alternativa é ter um tipo de dados abstrato, que se traduz em uma classe em linguagens semelhantes a Java / C #. O problema é que os idiomas principais têm suporte fraco para ADTs e você não pode alternar a implementação da classe sem o tomfoolery do sistema de arquivos. Mas se você deseja viver com isso, pode garantir que haja apenas uma implementação no sistema. Isso é um avanço das interfaces, nas quais seu código pode quebrar a qualquer momento e, quando isso acontecer, você terá que descobrir qual implementação da interface quebrou seu código e de onde veio.
EDIT : Observe que mesmo os hacks de IL não permitirão garantir a correção de certos aspectos de uma implementação. Por exemplo, é assumido implicitamente que
GetByID
deve retornar um valor ou lançar uma exceção. Alguém poderia escrever uma implementação que simplesmente entra em um loop sem fim e não o faz. Você não pode provar durante o tempo de execução que o código é repetido para sempre - se você conseguir fazer isso, resolveu o problema da parada. Você pode detectar casos triviais (por exemplo, reconhecer awhile (true) {}
), mas não um código arbitrário.fonte
Não há como 100% forçar o comportamento que você deseja, mas usando contratos de código, você poderia dizer que as implementações não devem retornar nulas, e talvez o objeto User tenha o ID passado (portanto, um objeto com o userid 0 falharia se 1 fosse passado ) Além disso, documentar o que você espera das implementações também pode ajudar.
fonte
Provavelmente é muito tarde para responder a essa pergunta, mas gostaria de compartilhar meu pensamento sobre isso. Como todas as respostas sugerem, não é possível forçar a restrição com a Interface. Mesmo com hacks seria difícil.
Uma maneira de conseguir isso, provavelmente específico para o .NET, é criar a interface como abaixo -
A tarefa não forçará a lançar certo tipo de exceção, mas tem disposição para transmitir a intenção de todo o implementador dessa interface. As tarefas no .NET têm certo padrão de uso, como Task.Result para acessar o resultado, Task.Exception, se houver exceção. Além disso, isso também permitirá ao usuário usá-lo de forma assíncrona.
fonte
Você não pode fazer isso com uma interface, como muitas outras já responderam.
Mas você pode fazer isso com uma classe base abstrata:
fonte