Este é o penúltimo post da série a respeito do S.O.L.I.D, caso ainda não tenha lido as demais partes, elas podem ser encontradas aqui:
Criando uma lógica de Login usando SOLID no Swift – Parte 1 de 5
Criando uma lógica de Login usando SOLID no Swift – Parte 2 de 5
Criando uma lógica de Login usando SOLID no Swift – Parte 3 de 5
Nesta série de posts sobre o uso do SOLID no Swift, estou abordando cada um dos princípios de forma única. Portanto, sem mais delongas, vamos continuar a avançar ainda mais na montagem deste quebra-cabeças com o princípio que costuma ser confundido com Injeção de Dependência, falaremos sobre o I: Interface Segregation Principle. O que em português conhecemos como o Princípio da Segregação da Interface.
Mas, o que é esse princípio?
No já citado livro do Uncle Bob, “Princípios, Padrões e Práticas Ágeis”, diz que o ISP (Interface segregation principle) lida com “as desvantagens das interfaces ‘gordas'”.
As interfaces da classe podem ser divididas em grupos de métodos. Cada grupo atende a um conjunto diferente de clientes. Assim, alguns clientes usam um grupo de métodos e outros clientes usam outros grupos.
Na prática o que podemos absorver é que uma classe não deve ser obrigada a implementar métodos de interfaces que não faz sentido para o seu contexto e/ou não irá utilizá-lo.
Vamos ver como isso funciona.
Analisando o código.
Na nossa view model, temos um objeto do tipo LoginWorkerProtocol
no qual usamos para realizar o request do login. Vamos analisar quais os métodos esse protocolo define:
LoginWorkerProtocol – revision 3
protocol LoginWorkerProtocol: AnyObject {
func requestLogin(with loginRequest: LoginRequest, completion: @escaping (Result<LoginResponse, ApiError>?) -> Void)
}
Note que temos apenas o método para requisitarmos o login. Agora vamos alterar o protocol para podermos permitir que também seja realizada a exclusão do usuário do storage do device. O LoginWorkerProtocol
ficará assim:
LoginWorkerProtocol – revision 4
protocol LoginWorkerProtocol: AnyObject {
func requestLogin(with loginRequest: LoginRequest, completion: @escaping (Result<LoginResponse, ApiError>?) -> Void)
func excludeUserFromStorage(_ email: String, completion: @escaping (Bool) -> Void)
}
A partir de agora, toda classe que implementa o LoginWorkerProtocol
será obrigada a implementar o método excludeUserFromStorage
, porém essa não é a responsabilidade de um serviço de login. Somente com essa alteração nós ferimos 3 princípios do SOLID: SRP, OCP e o ISP.
Vamos ver como ficou a classe LoginApiWorker
após as alterações do protocol:
LoginApiWorker – revision 4
final class LoginApiWorker: LoginWorkerProtocol {
func requestLogin(with loginRequest: LoginRequest, completion: @escaping (Result<LoginResponse, ApiError>?) -> Void) {
... // implementação do request via https.
}
func excludeUserFromStorage(_ email: String, completion: @escaping (Bool) -> Void) {
// Não tem nada pra ser implementado aqui, na API não temos um endpoint para excluir a sessão do usuário.
}
}
Esse novo método no protocol, está ferindo o ISP, fazendo com que todas as classes que implementam o LoginWorkerProtocol
tenham uma implementação do método excludeUserFromStorage
, sendo que nem todas as classes tem a possibilidade de implementá-lo.
Para resolver isso, vamos segregar a interface (protocol) LoginWorkerProtocol
, para que o método de exclusão vá para outro protocol.
LoginWorkerProtocol & DeleteUserProtocol – revision 5
protocol LoginWorkerProtocol: AnyObject {
func requestLogin(with loginRequest: LoginRequest, completion: @escaping (Result<LoginResponse, ApiError>?) -> Void)
}
protocol DeleteUserProtocol: AnyObject {
func excludeUserFromStorage(_ email: String, completion: @escaping (Bool) -> Void)
}
Desta forma, fazemos com que somente as classes responsáveis por apagar os usuários do device storage precisem implementar o método de exclusão. Isso torna nosso código mais coeso e organizado, sem contar na facilidade de adicionar/remover comportamentos e regras.
LoginInDeviceWorker – revision 5
final class LoginInDeviceWorker: LoginWorkerProtocol, DeleteUserProtocol {
func requestLogin(with loginRequest: LoginRequest, completion: @escaping (Result<LoginResponse, ApiError>?) -> Void) {
guard loginRequest.username == "xpto" else {
completion(.failure(.invalidCredentials))
return
}
completion(.success(LoginResponse(validated: true)))
}
func excludeUserFromStorage(_ email: String, completion: @escaping (Bool) -> Void) {
// Código para excluir o registro do usuário.
}
}
Conclusão
Criar interfaces específicas e especializadas ao invés de interfaces gerais e abrangentes, é crucial porque interfaces menores e focadas garantem que as classes que as implementam não sejam forçadas a depender de métodos que não utilizam, promovendo uma arquitetura de código mais coesa e menos acoplada. Essa prática facilita a manutenção, reduz o impacto de mudanças e melhora a testabilidade do sistema, uma vez que as modificações em uma interface específica têm menos probabilidade de afetar outras partes do código. Em essência, a Segregação de Interface contribui para um design mais robusto, flexível e fácil de entender.
Vale ressaltar que neste post consideramos apenas o ISP, porém, vimos que os princípios estão, cada vez mais, se complementando à medida que avançamos no entendimento de cada um deles. No próximo, e último, post desta série iremos finalizar esse caso de uso “Realizar Login”, para que esteja em conformidade com todos os princípios.
Se você chegou até aqui, obrigado pela atenção.
Quaisquer dúvidas, deixe nos comentários para enriquecermos o aprendizado.
Compartilhe este post com outros devs para que possamos trocar mais experiências.
Referências:
Princípios, Padrões e Práticas Ágeis em C#
The S.O.L.I.D Principles in Pictures
O que é SOLID: O guia completo para você entender os 5 princípios da POO