Hone logo
Hone
Problems

Crafting Decorator Factories in TypeScript

Decorator factories are a powerful pattern in TypeScript that allow you to create decorators dynamically based on arguments or configuration. This challenge will guide you in building a decorator factory that accepts a prefix string and applies it to the beginning of the return value of the decorated function. Understanding decorator factories is crucial for creating reusable and configurable decorators.

Problem Description

You are tasked with creating a decorator factory named withPrefix. This factory should accept a prefix string as an argument and return a decorator function. The returned decorator function, when applied to a function, should wrap the original function and modify its return value. Specifically, the decorator should prepend the provided prefix to the return value of the original function.

Key Requirements:

  • Decorator Factory: The solution must be a function that returns a decorator.
  • Prefix Application: The decorator must prepend the provided prefix to the return value of the decorated function.
  • Type Safety: The solution should maintain type safety, ensuring that the prefix is correctly applied to the return value's type.
  • Original Function Preservation: The original function's arguments and context (this) should be passed through correctly.

Expected Behavior:

When withPrefix is called with a prefix string, the returned decorator should, when applied to a function, return a new function that, when invoked, calls the original function, prepends the prefix to its return value, and returns the modified value.

Edge Cases to Consider:

  • The original function might not return a string. The decorator should handle this gracefully (e.g., by converting the return value to a string before prepending the prefix, or by returning the original value unchanged).
  • The prefix string might be empty.
  • The decorated function might be called with null or undefined as this.

Examples

Example 1:

Input:
const withPrefix = (prefix: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    const result = originalMethod.apply(this, args);
    return prefix + result;
  };
  return descriptor;
};

function greet() {
  return "World";
}

const decoratedGreet = withPrefix("Hello, ") (greet);

Output:
"Hello, World"
Explanation: The `withPrefix` factory creates a decorator that prepends "Hello, " to the return value of the `greet` function.

Example 2:

Input:
const withPrefix = (prefix: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    const result = originalMethod.apply(this, args);
    return prefix + String(result);
  };
  return descriptor;
};

class MyClass {
  add(a: number, b: number) {
    return a + b;
  }
}

const myInstance = new MyClass();
const decoratedAdd = withPrefix("Result: ") (MyClass.prototype, 'add');

Output:
"Result: 10"
Explanation: The `withPrefix` factory creates a decorator that prepends "Result: " to the return value of the `add` method, converting the result to a string first.

Example 3: (Edge Case - Non-String Return)

Input:
const withPrefix = (prefix: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
  const originalMethod = descriptor.value;
  descriptor.value = function(...args: any[]) {
    const result = originalMethod.apply(this, args);
    return prefix + String(result);
  };
  return descriptor;
};

function returnObject() {
  return { name: "Alice" };
}

const decoratedReturnObject = withPrefix("Object: ") (returnObject);

Output:
"Object: [object Object]"
Explanation: The decorator handles a non-string return value by converting it to a string using `String()`.

Constraints

  • The prefix string must be a string.
  • The solution must be written in TypeScript.
  • The solution should be reasonably efficient. Avoid unnecessary operations.
  • The solution must be type-safe.

Notes

  • Consider using TypeScript's decorator syntax (@decorator) for applying the decorator.
  • Think about how to handle different types of return values from the decorated function. Converting to a string is a common approach.
  • The descriptor object provides access to the original function's metadata (value, writable, enumerable, configurable). You'll need to modify the value property to replace the original function with the wrapped function.
  • Pay close attention to the this context and how arguments are passed to the original function. Use apply or call to ensure correct execution.
Loading editor...
typescript