DEV Community

Anh Trần Tuấn
Anh Trần Tuấn

Posted on • Originally published at tuanh.net on

The Secrets of Decomposition: The Philosophical Foundation of Programming

1. Introduce

In the early days of programming, during its very beginnings, people were still awkward with writing even the simplest lines of code. They used mechanical control panels or typed commands directly on a computer keyboard.

Assembly and Fortran had little to no concept of functions and methods like those found in modern programming languages. In Assembly, instructions and commands were arranged in the order of execution, corresponding to basic operations on the hardware.

Image

I still sense the emergence of something in their thinking, the early notions of "decomposition" in programming. Perhaps later concepts such as Classes, Interfaces, and Methods originated from the difficulties programmers faced while working with code. The high cost of programmers' time, coupled with the challenge of writing messy code and then spending considerable time tracking it down and fixing errors, didn’t offer much value to individuals or businesses.

2. SOLID

Source code is organized into multiple modules, each consisting of several classes, with each class containing multiple methods, and each method potentially having smaller sub-methods. However, each module, class, and method should ideally perform only one specific function. This is a way of dividing tasks during the process of project decomposition.

Task decomposition is similar to the Divide and Conquer strategy; it simplifies the implementation of functionalities and makes the system easier to extend.

Interface Segregation Principle, we move on to a more fundamental design principle: break down interfaces into the smallest possible units to ensure that we can implement all abstract methods in the interface. Here is an example:

public interface Poultry {
    Boolean hasTwoLegs(Object g);
    void eat(Object g);
    void breathe(Object g) 
}

public class Mallard implements Poultry {
    ....
}

public class PlasticDuck implements Poultry {
    ....
}
Enter fullscreen mode Exit fullscreen mode

In practice, the following code violates the Liskov Substitution Principle (LSP) because VitNhua cannot fully replace the Poultry interface. It can only implement a single abstract method, Boolean hasTwoLegs(Object g);. To address this issue, we " decompose" the large interface into the smallest possible interfaces.

public interface BirdShape{
    Boolean hasTwoLegs(Object g);
}

public interface PoultryAction{
    void eat(Object g);
    void breathe(Object g) 
}

public class Mallard implements BirdShape, PoultryAction{
    ....
}

public class PlasticDuck implements BirdShape{
    ....
}
Enter fullscreen mode Exit fullscreen mode

Java supports multiple inheritance through interfaces, so breaking down interfaces to describe the most general characteristics of a class is advisable. Decomposition in programming is the approach we should take.

Those who are familiar with Spring Framework or MicroProfile will likely be well-acquainted with it. The design of modules in these frameworks is based on the Dependency Inversion Principle. This means that modules are broken down and do not depend on each other; they are used by injecting them to reuse implemented functions. Each module is specialized and "decomposed" for a specific function and performs only that function.

2. OOP (Object-Oriented Programming)

2.1 Abstraction

For a long time, those learning about abstraction in OOP might think:
"Abstraction is selecting data from a larger group to only display the relevant details of an object to the user. It helps reduce complexity and programming effort. It is one of the most important concepts in OOP."

But here's the question: Why do it this way? Once you understand the problem, why not write the code directly instead of defining everything and making it more verbose?

The answer is "hardship before success, that's how you become rich." Just kidding!!

In reality, in large projects, having to read code is unavoidable. Therefore, being able to understand the code without reading it directly is the ultimate skill. The introduction of interfaces, as I mentioned earlier in the article about the origins of programming, is essential. With interfaces or abstraction, the separation between introduction and definition becomes clearer. The project layout becomes more robust. Let me give you an example to illustrate this. Just talking about it might not be convincing.

public class PaymentCalculation {
    public double calculateByCash() {} 

    public double calculateByCreditCard() {} 

    public double calculateByDebitCard() {} 

    public double calculateForPoorHousehold() {} 
}
Enter fullscreen mode Exit fullscreen mode

The way the code is written is not wrong at all, but don't you think it feels a bit "quick and dirty"? If you want to add more methods to describe Credit card, the class will become larger and larger, to the point where it becomes overwhelming and you might not want to refactor it anymore. Instead, you could implement it like this: each method does its job separately, making the code clean and easy to manage.

interface PaymentCalculator {
    double calculate();
}

public class CashPayment implements PaymentCalculator {
    public double calculate() {
    }
}

public class CreditCardPayment implements PaymentCalculator {
    public double calculate() {
    }
}
Enter fullscreen mode Exit fullscreen mode

There you go. With this design approach, I'm confident you'll find it easier to scale out your system in the future. That's why there's a saying, "A long road reveals the good horse." In summary, abstraction involves dividing logic and separating the process of defining from implementing, making it easier to develop your system later on.

2.2 Encapsulation

Modifiers such as public, private, and protected help you define the scope of a class or method, delineate boundaries, and encapsulate methods, ensuring security for the codebase. This represents the concept of scope in programming. Variables and methods in a given scope are only accessible within that scope.

3. Design Patterns and Programming Strategies

First, I want to discuss the divide-and-conquer strategy. Divide-and-conquer refers to solving a complex problem by breaking it into smaller, more manageable problems and then combining the solutions of these smaller problems to create a comprehensive solution. This strategy emphasizes dividing tasks and separating different parts of a problem to reduce complexity and increase reusability.

Additionally, there are several design patterns based on the concept of decomposition, including: Composite Pattern, Strategy Pattern, Template Method Pattern, Factory Pattern, and Observer Pattern.

4. Decomposition in Modern Programming

4.1 Single Page

Image

Single-page applications (SPAs) built with React or Angular are closely related to decomposition in programming. Both technologies aim to break down applications into smaller, manageable components for easier management and reuse. Decomposition is a design principle that separates application logic and functionality into independent, maintainable, and testable parts. React and Angular provide modularity and component-based approaches to implement decomposition, enhancing performance, scalability, and code management in modern web application development.

4.2 Microservice

Image

Microservices are tightly connected to decomposition in programming. Microservices architecture involves dividing an application into small, independent, and scalable services. Each service focuses on a specific task and communicates through lightweight protocols such as HTTP or message queues. Applying decomposition to microservices allows each service to be developed independently, using different technologies and languages as needed. This approach creates flexibility, scalability, and ease of management when building and deploying complex systems.

4.3 Faas

Image

In AWS, FAAS or AWS Lambda Functions are closely related to decomposition in programming. Lambda Functions is a cloud computing service that allows you to run code without managing servers. Decomposition in programming involves separating logic and functionality into smaller parts for easier maintenance and testing. With Lambda Functions, you can deploy independent functions like microservices. Each function performs a specific task and is triggered by events from various sources. Decomposition increases flexibility and scalability by deploying separate functions and paying only for the actual resources used.

4.4 Web3, Blockchain

Image

Web3 and blockchain are closely related to decomposition in programming. Web3 is a toolkit and API for interacting with blockchain technology. Blockchain, a distributed and secure technology, stores data in linked blocks and is managed by distributed network nodes. Decomposition in programming involves separating logic and functionality into smaller, maintainable, and testable parts. Each part of the application can be implemented as a smart contract or an independent service.

5. Conclusion

Although these are just basic examples, I hope you grasp the concept of decomposition in programming. If it weren’t so useful, it wouldn’t be employed so thoroughly from theory to real-world applications. I hope this helps you uncover something new.

Thank you for reading!

Best regards and wishing you success!

Read posts more at : The Secrets of Decomposition: The Philosophical Foundation of Programming

Top comments (0)