What Is Composable Code And How Can You Make It? – CloudSavvy IT

Photo of colorful wood blocksShutterstock.com/locrifa

Compilable code describes classes and functions that can be easily combined to create more powerful, higher-level constructs. Composability compares favorably with alternative forms of code reuse, such as object-oriented inheritance. It advocates creating small self-contained units that are treated as building blocks for larger systems.

Composability and inversion of control

Composable code is often a goal and effect of Inversion of Control (IoC) strategies. Techniques such as dependency injection work with self-contained components that are passed (“injected”) where they are needed. This is an example of IoC – the outside environment is responsible for resolving the dependencies of the deeper layers of code it calls.

The concept of composability encompasses the specific pieces you can provide and how they integrate with each other. A composable system will consist of several functional units each of which has a single responsibility. More complex processes are developed by “composing” several of these units into a new, larger one.

Examples of composability

Here is an example of three possible functional units:

interface LogMessage { public function getMessage() : string; } interface Mailable { public function getEmailContent() : string; } interface RelatesToUser { public function getTargetUserId() : int; †

Now let’s add a logger implementation to the mix:

interface Logger { public function log (LogMessage $message): void; } final class SystemErrorLogMessage implements LogMessage, Mailable { public function __construct (secure read-only \Exception $e) { } public function getMessage() : string { return “Unhandled error: “. $this -> e -> getMessage(); } public function getEmailContent() : string { return $this -> getMessage(); †

Now let’s look at another type of log message:

final class UserLoggedInLogMessage implements LogMessage, Mailable, RelatesToUser { public function __construct (secure read-only int $UserId) { } public function getMessage(): string { return “User {$this -> UserId} logged in!”; } public function getEmailContent() : string { return $this -> getMessage(); } public function getTargetUserId() : int { return $this -> UserId; †

Here we see the benefits of composability. By defining the functionality of the application as interfaces, concrete class implementations can be freely combined with the parts they need. Not every log message has an associated user; some messages may not qualify for email alerts if they are of low priority or contain sensitive information. By keeping the interfaces autonomous, you can create flexible implementations for any situation.

The above examples are written in PHP, but can be replicated in any object-oriented language. However, composability is not limited to OOP code: it is also a fundamental aspect of functional programming. Here, complex behaviors are obtained by linking small functions together. Functions can take other functions as arguments and return a new higher-order function as a result.

const square = x => (x * x); const quadruplicate = x => (x * 4); // 16 console.log(compose(square, quadruple)(2));

This minimal JavaScript example uses the compose function library to compose the square and quadruple units into another function that squares the input and then quadruples it. The compose() helper function accepts other functions to compose; it returns a new function that calls the string of inputs in sequence.

You will also encounter composability in modern composite development frameworks. Here’s an example of a simple set of React components:

const UserCard = ({user, children}) => (

{user.name}

{children}

† const UserAvatar = ({user}) => { if (user.avatarId) { return † } else return † † const UserCardWithAvatar = ({user}) => (

Each part is kept simple by only dealing with a specific part of the overall functionality. You can render the UserCard on its own or build a new variant with an avatar or other React component. The UserCard is not complicated by the logic responsible for displaying the correct avatar image file.

Composition vs Inheritance

Object-oriented languages ​​often achieve code reuse through inheritance. Choosing inheritance as your default strategy can be a costly mistake that makes it more difficult to maintain a project over time.

Most languages ​​do not support multiple inheritance, so your options for complex combinations of functionality are limited. Here’s the log message from previously reworked in a similar inheritance model:

class LogMessage implements LogMessage { public function __construct (public read-only string $message): void; } class LogMessageWithEmail extends LogMessage implementations Mailable { public function getEmailContent() : string { return “New log message: {$this -> message}”; } } class LogMessageWithUser extends LogMessage implementations RelatedToUser { public function __construct(public read-only string $message, public read-only int $userId) { } public function getTargetUserId() : int { return $this -> userId; †

These lessons may seem helpful to begin with. Now you don’t need specific implementations of log messages, such as our UserLoggedInMessage class. However, there is one big problem: if you need to write a log message related to a user and send an email, there is no class for that. You could write a LogMessageWithEmailAndUser, but you’d start slithering every possible permutation with “generic” concrete class implementations.

Despite these issues, code that uses inheritance for this type of relationship model remains common in projects large and small. It has valid use cases, but is often implemented for the wrong reasons. Small composable units based on interfaces and functions are more versatile, make you think about the bigger picture within your system, and tend to create more maintainable code with fewer side effects.

A good rule of thumb for inheritance is to use it when an object is something else. Composition is usually the better choice when an object has something different:

Log/Email – A log message is not necessarily an e-mail, but may have e-mail content attached to it. The log must contain the email content as a dependency. If not all logs have an email component, then the composition should be used as shown above. User / Admin – The Admin inherits all the User’s behavior and adds a few new ones. It can be a good thing for inheritance – admin extends user.

Aiming for inheritance too early can limit you later as you find more unique scenarios in your application. Keeping functional units as small as possible, defining them as interfaces, and creating concrete classes that mix and match these interfaces is a more efficient way to conceptualize complex systems. It makes your components easier to reuse in different locations.

Overview

Compilable code refers to a resource that combines self-contained modular units into larger chunks of functionality. It is an embodiment of “has-a” relationships between different entities. The actual composition mechanism depends on the paradigm you use; OOP languages ​​require programming to interfaces rather than concrete classes, while functional domains often lead you to good composability by design.

Being proactive in using composable techniques leads to more resilient code that is loosely coupled, easier to reason, and more adaptable to future use cases. Creating composable blocks is often the most effective starting point when refactoring large-scale systems. While alternatives such as inheritance also have valid roles, they are less widely applicable and more prone to abuse than composability, dependency injection, and IoC.

This post What Is Composable Code And How Can You Make It? – CloudSavvy IT

was original published at “https://www.cloudsavvyit.com/15780/what-is-composable-code-and-how-can-you-create-it/”