Home >Web Front-end >JS Tutorial >(D): Applying the \'Dependency Inversion Principle\' with Typescript and Java

(D): Applying the \'Dependency Inversion Principle\' with Typescript and Java

Susan Sarandon
Susan SarandonOriginal
2024-12-03 12:00:20477browse

(D): Aplicando o

Concepts

SOLID is an acronym that represents five fundamental principles of object-oriented programming, proposed by Robert C. Martin - Uncle Bob. Here you can read more about his article.
These principles aim to improve the structure and maintenance of code, making it more flexible, scalable, and easier to understand. Such principles help the programmer to create more organized codes, dividing responsibilities, reducing dependencies, simplifying the refactoring process and promoting code reuse.

The "D" in the acronym stands for "Dependency Inversion Principle". The phrase that uncle bob used to define this principle was:

"High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions"

The Principle of Dependency Inversion aims to reduce coupling between the components of a system, promoting greater flexibility, maintainability and testability.

Problems that DIP Solves

  • Tight coupling: When a module depends directly on a concrete implementation, changes to that implementation can affect other modules.
  • Difficulty of testing: Testing code units directly coupled to specific implementations is more complicated, as it requires the use of these concrete implementations, making it difficult to create mocks or stubs.
  • Low reusability: A module highly coupled to concrete details is less reusable in other contexts.

Practical Application

We will create a code responsible for sending notifications via email, in order to analyze the problems and possible solutions to solve them

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!");

Problems:

  • The Notification class directly depends on a concrete implementation (EmailService).
  • If we want to change the notification channel (e.g.: SMS), we need to change the Notification code.

Solutions and Benefits:

  • Notification does not need to know details about how the message is sent.
  • Ease of replacing or adding new communication channels.
  • We can test Notification in isolation, without relying on real implementations.

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. Unit Tests

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");

Conclusion

The Dependency Inversion Principle (DIP) is a fundamental pillar for flexible and robust projects. It allows you to reduce coupling between classes, facilitate code reuse and improve application testability. By relying on abstractions, your system becomes more adaptable to change and expandable with new features. The practical example demonstrated how small design adjustments can solve recurring maintenance problems. Applying DIP in conjunction with other SOLID principles ensures cleaner code that is ready for growth. Adopting these concepts is essential for developers seeking excellence in software architecture.

Bibliographic References

  • Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 2002.
  • Thiago Leite and Carvalho. Object Orientation. Casa do Code, 2014.

The above is the detailed content of (D): Applying the \'Dependency Inversion Principle\' with Typescript and Java. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn