DEV Community

Victor Lima Reboredo
Victor Lima Reboredo

Posted on

(D): Aplicando o "Princípio da Inversão de Dependências" com Typescript e Java

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!");
    }
}
Enter fullscreen mode Exit fullscreen mode

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!");
Enter fullscreen mode Exit fullscreen mode

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!");
    }
}
Enter fullscreen mode Exit fullscreen mode

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!");
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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)