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
prefixto 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
prefixstring might be empty. - The decorated function might be called with
nullorundefinedasthis.
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
prefixstring 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
descriptorobject provides access to the original function's metadata (value, writable, enumerable, configurable). You'll need to modify thevalueproperty to replace the original function with the wrapped function. - Pay close attention to the
thiscontext and how arguments are passed to the original function. Useapplyorcallto ensure correct execution.