🚀 Revisando o S.O.L.I.D na Prática com Flutter e Dart
🎯 Introdução
Resolvi revisar os meus conhecimentos sobre temas essenciais da programação. Iniciei pelo SOLID porque é algo que deve ser revisitado de vez em quando para garantir que estamos entendendo não só o conceito, mas a prática também.
Com isso, podemos escrever códigos mais limpos, escaláveis e de fácil entendimento.
🛠️ Metodologia
Usei como base dois repositórios meus de quando eu estava estudando Clean Architecture. Um deles trata-se de um app simples: buscar conselhos em uma API e retornar para o usuário.
Neste projeto, aplico cada um dos cinco princípios do SOLID. Abaixo, detalho como cada um deles se comporta no código.
🟢 S — Single Responsibility Principle (SRP)
Princípio da Responsabilidade Única: Uma classe deve ter um, e apenas um, motivo para mudar.
No exemplo abaixo, a classe abstrata para o data source define apenas o contrato de busca remota. Ela não se preocupa com lógica de negócio ou como os dados serão exibidos.
abstract class AdviceRemoteDataSource {
Future<Advice>? getAdvice();
}
Outro exemplo claro é o tratamento de erros. Criamos uma estrutura base e especializamos as falhas:
abstract class Failure extends Equatable {
const Failure([List properties = const <dynamic>[]]) : super();
}
// Responsável apenas por erros de servidor
class ServerFailure extends Failure {
@override
List<Object?> get props => [];
}
// Responsável apenas por erros de cache local
class CacheFailure extends Failure {
@override
List<Object?> get props => [];
}
🔵 O — Open/Closed Principle (OCP)
Aberto para Extensão, Fechado para Modificação: Você deve ser capaz de estender o comportamento de uma classe sem modificar o seu código-fonte original.
A classe AdviceRepository está "fechada" (o contrato está definido). Se precisarmos de uma nova forma de obter dados, apenas implementamos o contrato em uma nova classe, sem tocar na interface original.
abstract class AdviceRepository {
Future<Either<Failure, Advice?>>? getAdvice();
}
class AdviceRepositoryImpl implements AdviceRepository {
final AdviceRemoteDataSource remoteDataSource;
AdviceRepositoryImpl({required this.remoteDataSource});
@override
Future<Either<Failure, Advice?>>? getAdvice() async {
try {
final remoteAdvice = await remoteDataSource.getAdvice();
return Right(remoteAdvice);
} on ServerException {
return Left(ServerFailure());
}
}
}
🟡 L — Liskov Substitution Principle (LSP)
Princípio da Substituição de Liskov: Objetos de uma superclasse devem ser substituíveis por objetos de suas subclasses sem quebrar a aplicação.
No Flutter, isso é muito comum com os DataSources. A implementação AdviceRemoteDataSourceImpl pode substituir perfeitamente a abstração AdviceRemoteDataSource em qualquer lugar que ela seja exigida.
class AdviceRemoteDataSourceImpl implements AdviceRemoteDataSource {
final http.Client client;
AdviceRemoteDataSourceImpl({required this.client});
@override
Future<Advice>? getAdvice() async {
// Implementação real da chamada HTTP
// ...
}
}
💡 Obs.: Uma melhoria seria criar um AdviceDataSource genérico, permitindo trocar o Remote pelo Local de forma transparente.
🟠 I — Interface Segregation Principle (ISP)
Segregação de Interface: Uma classe não deve ser forçada a implementar interfaces e métodos que não utiliza.
Em vez de criar uma interface gigante "TudoNoApp", dividimos em interfaces específicas. No exemplo, o repositório depende apenas do que é essencial para ele: o DataSource de conselhos.
// Interface específica e focada no domínio de conselhos
abstract class AdviceRemoteDataSource {
Future<Advice>? getAdvice();
}
🔴 D — Dependency Inversion Principle (DIP)
Inversão de Dependência: Dependa de abstrações, não de implementações concretas.
Aqui é onde o GetIt ou Provider brilham. O AdviceRepositoryImpl não conhece a implementação do DataSource, ele conhece apenas o contrato. A "mágica" acontece no nosso Service Locator:
void setupLocator() {
// Registramos a abstração apontando para a implementação
getIt.registerLazySingleton<AdviceRepository>(
() => AdviceRepositoryImpl(remoteDataSource: getIt()),
);
getIt.registerLazySingleton<AdviceRemoteDataSource>(
() => AdviceRemoteDataSourceImpl(client: getIt()));
getIt.registerLazySingleton(() => http.Client());
}
📊 Resumo Visual
| Sigla | Princípio | Resumo em uma frase |
|---|---|---|
| S | Responsabilidade Única | Cada classe faz apenas uma coisa. |
| O | Aberto/Fechado | Estenda o comportamento sem alterar o original. |
| L | Subst. de Liskov | Subclasses devem honrar o contrato da base. |
| I | Segregação de Interface | Crie interfaces pequenas e específicas. |
| D | Inversão de Dependência | Dependa de contratos, não de classes concretas. |
🏁 Conclusão
Utilizar esses conceitos na prática vai além de construir "códigos bonitos". Trata-se de garantir a manutenibilidade. Um código bem estruturado permite que ele viva mais e que as manutenções futuras não sejam um sofrimento.
Lembre-se: o seu código fala muito sobre você! 🚀
Ficou com alguma dúvida sobre como aplicar isso no seu projeto Flutter? Comenta aqui embaixo e vamos trocar uma ideia!
Top comments (0)