GRASP in Javascript functional programming

ohanhaliuk

Oleksandr Hanhaliuk

Posted on August 13, 2024

GRASP in Javascript functional programming

In the world of software development, design patterns serve as time-tested solutions to common problems.
One of the less commonly discussed but equally vital sets of design patterns is GRASP (General Responsibility Assignment Software Patterns). Principles of GRASP often correlate with SOLID principles and other OOP design patterns.
GRASP, or General Responsibility Assignment Software Patterns, is a collection of guidelines aimed at assigning responsibilities to classes and objects in object-oriented design. How can we use these patterns in our Javascript (Node.js) development? Of course, Javascript supports classes, which are natively built on prototypes, where we can apply GRASP similar way as we would do in Java.
However, in my opinion, GRASP patterns can also be applied to functional programming.

What is GRASP?

The nine GRASP patterns are:

  1. Information Expert
  2. Creator
  3. Controller
  4. Low Coupling
  5. High Cohesion
  6. Polymorphism
  7. Pure Fabrication
  8. Indirection
  9. Protected Variations

Information Expert

Assign responsibilities to functions that have the required data or knowledge to perform a task. In functional programming, this principle can be applied by assigning responsibilities to functions or modules that have the data or context required to perform a task.

// User management module
const createUser = (name, email) => ({ name, email });

const getUserEmail = (user) => user.email;

const updateUserEmail = (user, newEmail) => ({
  ...user,
  email: newEmail,
});

const user = createUser('John Doe', 'john@example.com');
console.log(getUserEmail(user));  // 'john@example.com'

const updatedUser = updateUserEmail(user, 'john.doe@example.com');
console.log(getUserEmail(updatedUser));  // 'john.doe@example.com'
Enter fullscreen mode Exit fullscreen mode

Creator

Use factory functions to create complex data structures or objects. In functional programming, while we don’t deal with classes in the same way as in object-oriented programming, we can apply the Creator principle by assigning the responsibility of creating data structures or initializing objects to functions or modules that have the necessary information and context.

const createUser = (name, email) => ({ name, email });
Enter fullscreen mode Exit fullscreen mode

Controller

Use higher-order functions to handle system events and delegate tasks. In functional programming, controllers often take the form of functions that orchestrate the flow of data and actions between different parts of the system, ensuring that responsibilities are clearly separated.

// Example of express.js controller
const handleRequest = (req, res, userService) => {
  const user = userService.createUser(req.body.name, req.body.email);
  res.send(user);
};
Enter fullscreen mode Exit fullscreen mode

Low Coupling

Ensure functions are independent and only depend on explicit inputs. In functional programming, low coupling is achieved by designing functions and modules that operate independently of each other, with minimal reliance on the internal details of other functions or modules

const sendEmail = (emailService, email) => emailService.send(email);
Enter fullscreen mode Exit fullscreen mode

High Cohesion

High Cohesion refers to the degree to which the elements within a module or function belong together. In functional programming, achieving high cohesion means designing functions and modules so that they perform a single, well-defined task or closely related set of tasks.

const createUser = (name, email) => ({ name, email });
const addUser = (users, user) => [...users, user];

const createAndAddUser = (users, name, email)=>{
  const user = createUser(name, email);
  return addUser(users, user)
}
// usage
const users = [
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' },
];

const newUsers = createAndAddUser(users, 'Charlie', 'charlie@example.com');

Enter fullscreen mode Exit fullscreen mode

Polymorphism

Use higher-order functions and first-class functions to achieve polymorphism. In functional programming, polymorphism is typically achieved through higher-order functions, generic functions, and type systems like Typescript

const processPayment = (paymentMethod) => paymentMethod.process();
Enter fullscreen mode Exit fullscreen mode

Pure Fabrication

Create utility functions that do not directly correspond to domain concepts but provide necessary functionality, when no suitable domain function or class exists.

const log = (message) => console.log(message);
Enter fullscreen mode Exit fullscreen mode

Indirection

Indirection in functional programming refers to the use of intermediate functions to manage interactions between different parts of a system. A good example in Node.js can be the Middleware Pattern.

const withNumberFilterMiddleware = (data) => data.filter(item => !isNaN(Number(item)));
Enter fullscreen mode Exit fullscreen mode

Protected Variations

Protected Variations in functional programming mean creating a design that is resilient to changes by encapsulating the parts that vary and ensuring that the rest of the system is protected from these variations. In functional programming, this principle can be applied through the use of abstraction, immutability, and encapsulation to create robust and maintainable code that is less susceptible to changes.

const processCreditCardPayment = (amount) => {
  console.log(`Processing credit card payment of ${amount}`);
  // Credit card payment logic
};

const processPayPalPayment = (amount) => {
  console.log(`Processing PayPal payment of ${amount}`);
  // PayPal payment logic
};

const processPayment = (paymentMethod, amount) => {
  paymentMethod(amount);
};

// Use different payment methods without changing the processPayment function
processPayment(processCreditCardPayment, 100);
processPayment(processPayPalPayment, 200);
Enter fullscreen mode Exit fullscreen mode

Summary

As you can see GRASP principles are correlating with many known design patterns as well as SOLID principles. High Cohesion is almost equal to the Single Responsibility principle and so on.
Those principles are not only OOP principles but general principles for programming well-architected clean code, whether its functional or OOP programming.

💖 💪 🙅 🚩
ohanhaliuk
Oleksandr Hanhaliuk

Posted on August 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related