O título da pergunta é provavelmente muito abstrato, portanto, deixe-me fornecer um exemplo específico do que tenho em mente:
Existe um webservice que encapsula um processo de alteração de senhas para usuários de um sistema distribuído. O webservice aceita o login do usuário, sua senha antiga e uma nova senha. Com base nessa entrada, ele pode retornar um dos três resultados a seguir:
- Caso o usuário não seja encontrado, ou sua senha antiga não corresponda, ele simplesmente retornará com HTTP 403 Forbidden.
- Caso contrário, é necessária uma nova senha e a conformidade com uma política de senha (por exemplo, é longa o suficiente, contém uma combinação adequada de letras e números etc.). Caso contrário, retornará um XML descrevendo porque a senha não está em conformidade com a política.
- Caso contrário, ele alterará a senha e retornará um XML contendo uma data de expiração da nova senha.
Agora, gostaria de criar uma classe, idealmente com um único método, para encapsular o trabalho com esse serviço da web. Minha primeira foto foi esta:
public class PasswordManagementWebService
{
public ChangePasswordResult ChangePassword(string login, string oldPassword, string newPassword)
{
ChangePasswordResult result;
// send input to websevice, it's not important how; the httpResponse
// will contain a response from webservice
var httpResponse;
if (HasAuthenticationFailed(httpResponse)
{
throw new AuthenticationException();
}
else if (WasPasswordSuccessfullyChanged(httpResponse))
{
result = new ChangePasswordSuccessfulResult(httpResponse);
}
else
{
result = new ChangePasswordUnsuccessfulResult(httpResponse);
}
return result;
}
}
public abstract class ChangePasswordResult
{
public abstract bool WasSuccessful { get; }
}
public abstract class ChangePasswordSuccessfulResult
{
public ChangePasswordSuccessfulResult(HttpResponse httpResponse)
{
// initialize the class from the httpResponse
}
public override bool WasSuccessful { get { return true; } }
public DateTime ExpirationDate { get; private set; }
}
public abstract class ChangePasswordUnsuccessfulResult
{
public ChangePasswordUnsuccessfulResult(HttpResponse httpResponse)
{
// initialize the class from the httpResponse
}
public override bool WasSuccessful { get { return false; } }
public bool WasPasswordLongEnough { get; private set; }
public bool DoesPasswordHaveToContainNumbers { get; private set; }
// ... etc.
}
Como você pode ver, eu decidi usar classes separadas para casos de retorno # 2 e # 3 - eu poderia ter usado uma única aula com um booleano, mas pareceria um cheiro, a classe não teria um propósito claro . Com duas classes separadas, um usuário da minha classe PasswordManagementWebService
agora precisa saber quais classes herdam de ChangePasswordResult
e converter para uma classe correta com base na propriedade WasSuccessful
. Embora agora eu tenha uma boa aula focada em laser, tornei a vida dos meus usuários mais difícil do que deveria.
Quanto ao caso # 1, decidi lançar uma exceção. Eu poderia ter criado uma exceção separada para o caso # 2, também, e só retornar algo do método quando a senha foi alterada com sucesso. No entanto, isso não parece certo - não acho que uma nova senha sendo inválida seja um estado excepcional o suficiente para justificar uma exceção.
Não sei ao certo como é que eu criaria as coisas se houvesse mais de dois tipos de resultados não excepcionais do serviço Web. Provavelmente, eu alteraria um tipo de propriedade WasSuccessful
de booleano para um enum e o renomeia para ResultType
, adicionando uma classe dedicada herdada de ChangePasswordResult
para cada possível ResultType
.
Por último, para a pergunta atual: Essa abordagem de design (ou seja, ter uma classe abstrata e forçar os clientes a converter em um resultado correto com base em uma propriedade) é correta ao lidar com problemas como esse ? Se sim, existe uma maneira de melhorá-lo (talvez com uma estratégia diferente para quando lançar exceções versus resultados de retorno)? Se não, o que você recomendaria?