Conceitos
SOLID é um acrônimo que representa cinco princípios fundamentais da programação orientada a objetos, propostos por Robert C. Martin - o uncle Bob. Aqui você pode ler mais sobre o artigo dele.
Esses princípios têm como objetivo melhorar a estrutura e a manutenção do código, tornando-o mais flexível, escalável e fácil de entender. Tais princípios auxiliam o programador a criar códigos mais organizados, dividindo responsabilidades, reduzindo dependências, simplificando o processo de refatoração e promovendo a reutilização do código.
O "D" do acrônimo significa "Dependency Inversion Principle". A frase que o uncle bob utilizou para definir esse princípio foi:
"Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações"
O Princípio da Inversão de Dependências visa reduzir o acoplamento entre os componentes de um sistema, promovendo maior flexibilidade, manutenibilidade e testabilidade.
Problemas que o DIP Resolve
- Acoplamento rígido: Quando um módulo depende diretamente de uma implementação concreta, mudanças nessa implementação podem afetar outros módulos.
- Dificuldade de testes: Testar unidades de código acopladas diretamente a implementações específicas é mais complicado, pois exige o uso dessas implementações concretas, dificultando a criação de mocks ou stubs.
- Baixa reusabilidade: Um módulo altamente acoplado a detalhes concretos é menos reutilizável em outros contextos.
Aplicação Prática
Vamos criar um código responsável por enviar notificações via e-mail, de modo a analisar os problemas e as soluções possíveis para os solucionar
Java
class EmailService {
public void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
}
class Notification {
private EmailService emailService;
public Notification() {
this.emailService = new EmailService();
}
public void notify(String message) {
this.emailService.sendEmail(message);
}
}
// Uso
public class Main {
public static void main(String[] args) {
Notification notification = new Notification();
notification.notify("Welcome to our service!");
}
}
Typescript
class EmailService {
sendEmail(message: string): void {
console.log(`Sending email: ${message}`);
}
}
class Notification {
private emailService: EmailService;
constructor() {
this.emailService = new EmailService();
}
notify(message: string): void {
this.emailService.sendEmail(message);
}
}
// Uso
const notification = new Notification();
notification.notify("Welcome to our service!");
Problemas:
- A classe
Notification
depende diretamente de uma implementação concreta (EmailService
). - Se quisermos mudar o canal de notificação (ex.: SMS), precisamos alterar o código de
Notification
.
Soluções e Benefícios:
-
Notification
não precisa saber detalhes sobre como a mensagem é enviada. - Facilidade de substituir ou adicionar novos canais de comunicação.
- Podemos testar
Notification
isoladamente, sem depender de implementações reais.
Java
public interface MessageService {
void sendMessage(String message);
}
public class EmailService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending email: " + message);
}
}
public class SMSService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
public class Notification {
private final MessageService messageService;
public Notification(MessageService messageService) {
this.messageService = messageService;
}
public void notify(String message) {
messageService.sendMessage(message);
}
}
// Uso
public class Main {
public static void main(String[] args) {
Notification emailNotification = new Notification(new EmailService());
emailNotification.notify("Welcome via Email!");
Notification smsNotification = new Notification(new SMSService());
smsNotification.notify("Welcome via SMS!");
}
}
Typescript
interface MessageService {
sendMessage(message: string): void;
}
class EmailService implements MessageService {
sendMessage(message: string): void {
console.log(`Sending email: ${message}`);
}
}
class SMSService implements MessageService {
sendMessage(message: string): void {
console.log(`Sending SMS: ${message}`);
}
}
class Notification {
private messageService: MessageService;
constructor(messageService: MessageService) {
this.messageService = messageService;
}
notify(message: string): void {
this.messageService.sendMessage(message);
}
}
// Uso
const emailNotification = new Notification(new EmailService());
emailNotification.notify("Welcome via Email!");
const smsNotification = new Notification(new SMSService());
smsNotification.notify("Welcome via SMS!");
3. Testes Unitários
Java
public class MockMessageService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Mock message sent: " + message);
}
}
// Teste com o mock
public class Main {
public static void main(String[] args) {
MessageService mockMessageService = new MockMessageService();
Notification mockNotification = new Notification(mockMessageService);
mockNotification.notify("Test message");
}
}
Typescript
class MockMessageService implements MessageService {
sendMessage(message: string): void {
console.log(`Mock message sent: ${message}`);
}
}
// Teste com o mock
const mockNotification = new Notification(new MockMessageService());
mockNotification.notify("Test message");
Conclusão
O Princípio da Inversão de Dependências (DIP) é um pilar fundamental para projetos flexíveis e robustos. Ele permite reduzir o acoplamento entre classes, facilitar a reutilização de código e melhorar a testabilidade das aplicações. Ao depender de abstrações, seu sistema se torna mais adaptável a mudanças e expansível com novas funcionalidades. O exemplo prático demonstrou como pequenos ajustes no design podem resolver problemas recorrentes de manutenção. Aplicar o DIP em conjunto com outros princípios SOLID garante um código mais limpo e preparado para o crescimento. Adotar esses conceitos é essencial para desenvolvedores que buscam excelência em arquitetura de software.
Referências Bibliográficas
- Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 2002.
- Thiago Leite e Carvalho. Orientação a Objetos. Casa do Código, 2014.
Top comments (0)