Sunday, 26 January 2025

Why Are Design Patterns Important?

Design patterns play a crucial role in software engineering. They provide proven solutions to common problems, ensuring that software systems are robust, maintainable and scalable. In this post, I’ll discuss two key reasons why design patterns are essential for creating better software systems.

1. Ensuring SOLID and Other Principles

Most software engineers are familiar with the SOLID principles and their importance in writing clean, maintainable and scalable code. Violating these principles can lead to tightly coupled systems, poor maintainability and a lack of clarity in the codebase.

But how do we ensure that our code adheres to SOLID, DRY (Don’t Repeat Yourself), KISS (Keep It Simple, Stupid) and other best practices? This is where design patterns come into play.

By applying the right design pattern, we can create systems that naturally follow these principles. Let’s consider an example to understand this better:

Example: Payment System Without Design Patterns

Here’s a code snippet for a payment system that handles multiple payment methods (Credit Card, PayPal, Bitcoin) within a single class using conditional logic:


class PaymentProcessor:
    def __init__(self, payment_method, **kwargs):
        self.payment_method = payment_method
        self.payment_details = kwargs

    def process_payment(self, amount):
        if self.payment_method == "credit_card":
            card_number = self.payment_details.get("card_number")
            cardholder_name = self.payment_details.get("cardholder_name")
            if not card_number or not cardholder_name:
                raise ValueError("Credit Card details are missing!")
            return f"Paid ${amount} using Credit Card ({cardholder_name})."

        elif self.payment_method == "paypal":
            email = self.payment_details.get("email")
            if not email:
                raise ValueError("PayPal email is missing!")
            return f"Paid ${amount} using PayPal ({email})."

        elif self.payment_method == "bitcoin":
            wallet_address = self.payment_details.get("wallet_address")
            if not wallet_address:
                raise ValueError("Bitcoin wallet address is missing!")
            return f"Paid ${amount} using Bitcoin (Wallet: {wallet_address})."

        else:
            raise ValueError(f"Unsupported payment method: {self.payment_method}")

While functional, this approach violates key design principles:

  • Single Responsibility Principle (SRP): Payment validation and processing logic are mixed within the same class.
  • Open/Closed Principle (OCP): Adding a new payment method requires modifying the existing class.
  • Don’t Repeat Yourself (DRY): Validation and payment handling logic are duplicated across methods.

This tightly coupled and cluttered approach makes the system harder to extend, maintain, and test.

Solution: Strategy Design Pattern

By using a design pattern like the Strategy Pattern, we can create a more modular and maintainable solution. In this approach, each payment method has its own dedicated class implementing a common interface, and the PaymentProcessor delegates the payment logic to the appropriate class. This adheres to SOLID principles and creates a clean, extensible system.

2. Promoting Consistency and Collaboration

In real-world projects, multiple engineers often work on different parts of the system. Without a common approach, everyone might write code in their own style, leading to inconsistency and potential problems.

Example: Endpoint Development Without Design Patterns

Imagine two engineers working on different endpoints:

  • One engineer creates an endpoint for user creation.
  • Another engineer develops an endpoint for user updates.

If both engineers write similar operations independently without a shared structure, duplicate code is likely to emerge. This duplication increases maintenance efforts and introduces unnecessary complexity.

By following design patterns, engineers can work within a disciplined framework. They will use a common structure and shared solutions, reducing redundancy and making the project more organized and easier to maintain.

Final Thoughts

These two points highlight why design patterns are so important:

  1. They ensure adherence to best practices, such as SOLID, DRY, and KISS principles.
  2. They promote consistency and collaboration, especially in teams with multiple engineers working on the same project.

By leveraging design patterns, we can create systems that are easier to extend, maintain, and scale.

In my next article, I’ll dive deeper into the Strategy Design Pattern with a real-life example, explaining how it works and how it can solve problems effectively.

Stay tuned for more insights on design patterns and their use cases!

Friday, 27 October 2023

Reversing a String in JavaScript with Array Destructuring

Reversing a string in JavaScript is a common programming task, and there are multiple ways to accomplish it. In this blog post, we'll explore a simple and elegant method using array destructuring and the `join` method. This technique not only achieves the desired result but also provides insights into array manipulation in JavaScript.

The Approach

Let's dive into the process of reversing a string step by step:

  1. We start with the original string, "Hello, World!".
  2. We use the concise approach [...originalString].reverse().join('') to directly reverse the characters in the string without the need for an intermediate variable.

By breaking it down into these steps, we can better understand how the method works and why it's efficient.

Code Example

To illustrate the technique, let's take the string "Hello, World!" as an example:

      
        const originalString = "Hello, World!";
        const reversedString = [...originalString].reverse().join('');
        console.log(reversedString); // Output: "!dlroW ,olleH"
      
    

The output in this example will be "!dlroW ,olleH," which is the reverse of the original string "Hello, World!".

Why Use This Method?

Reversing a string using array destructuring and the `join` method offers several advantages:

  • Simplicity: The code is concise and easy to understand.
  • Efficiency: It avoids the need for an intermediate variable, making it efficient and memory-friendly.
  • Array Manipulation: This method is a great way to learn about array manipulation in JavaScript, as it combines several array operations in a single line of code.

Conclusion

Reversing a string is a fundamental task in programming, and JavaScript provides elegant solutions like array destructuring and the `join` method to accomplish it. By understanding these techniques, you not only solve a common problem but also enhance your skills in working with arrays.

Why Are Design Patterns Important?

Design patterns play a crucial role in software engineering. They provide proven solutions to common problems, ensuring that software system...