Injeção de dependência
A injeção de dependência é quando uma classe recebe de fora os objetos que ela precisa usar, em vez de criar esses objetos internamente.
A palavra "dependência" significa algo que uma classe precisa para funcionar, exemplo:
UsuarioService precisa de um Logger.
UsuarioService depende de Logger.
Existem duas formas de fazer isso:
Sem injeção A classe cria suas dependências internamente.
Com injeção A classe recebe suas dependências prontas.
Exemplo sem injeção de dependência
class Logger {
void info(String mensagem) {
print('[INFO] $mensagem');
}
}
class UsuarioService {
final Logger logger = Logger();
void criarUsuario(String nome) {
logger.info('Criando usuário $nome');
}
}
void main() {
final service = UsuarioService();
service.criarUsuario('Fulano');
}
Aqui, a própria classe UsuarioService cria o Logger:
final Logger logger = Logger();
Isso funciona, mas cria um acoplamento maior, ou seja, o UsuarioService fica preso diretamente à classe Logger. Essa abordagem cria um problema. Imagine que depois você queira trocar o logger.
Com o Logger criado dentro do UsuarioService, fica mais difícil trocar esse comportamento. Um exemplo melhor seria:
class Logger {
void info(String mensagem) {
print('[INFO] $mensagem');
}
}
class UsuarioService {
final Logger logger;
UsuarioService(this.logger);
void criarUsuario(String nome) {
logger.info('Criando usuário $nome');
}
}
void main() {
final logger = Logger();
final service = UsuarioService(logger);
service.criarUsuario('Fulano');
}
Agora o UsuarioService não cria mais o Logger, ele apenas declara que precisa de um (final Logger logger;). Depois ele recebe esse objeto pelo construtor:
UsuarioService(this.logger);
No main, o objeto é criado fora:
final logger = Logger();
final service = UsuarioService(logger);
Isso é injeção de dependência.
Comparação com Python
Como eu tenho maior contato com Python, vou fazer um exemplo para tentar facilitar o entendimento. A ideia seria parecida:
class Logger:
def info(self, mensagem):
print(f'[INFO] {mensagem}')
class UsuarioService:
def __init__(self, logger):
self.logger = logger
def criar_usuario(self, nome):
self.logger.info(f'Criando usuário {nome}')
logger = Logger()
service = UsuarioService(logger)
service.criar_usuario('Fulano')
A lógica é a mesma, a diferença é que em Dart você normalmente declara o tipo, como: final Logger logger;. Enquanto que em Python o tipo não é obrigatório.
Organização de projetos
A organização de projeto é a forma como você separa os arquivos do sistema. Em projetos pequenos, dá para colocar tudo no main.dart, mas conforme o código cresce, isso fica ruim para manter.
A ideia é separar responsabilidades:
bin/
app.dart
lib/
models/
usuario.dart
services/
usuario_service.dart
repositories/
usuario_repository.dart
utils/
logger.dart
Em Dart, normalmente:
bin/Fica para arquivos executáveis, como o ponto de entrada da aplicação.lib/Fica para o código reutilizável do projeto.
Mas isso não é uma regra, apenas uma convenção.
A estrutura é parecida com o exemplo abaixo:
meu_app/
├── bin/
│ └── app.dart
└── lib/
├── models/
│ └── usuario.dart
├── repositories/
│ └── usuario_repository.dart
├── services/
│ └── usuario_service.dart
└── utils/
└── logger.dart