TypeScript Protocol Implementation
This challenge focuses on understanding and implementing "protocol types" in TypeScript, a concept that allows you to define a set of required properties and methods that a type must conform to, without necessarily inheriting from a specific class. This is crucial for creating flexible and interoperable code, especially when dealing with external data or libraries where you can't control the exact class definitions.
Problem Description
Your task is to create a TypeScript system that allows you to define and enforce "protocol types" for objects. A protocol type specifies a contract that an object must fulfill, meaning it must possess certain properties with specific types and potentially certain methods. You should implement a mechanism to check if a given object conforms to a defined protocol.
Key Requirements:
- Define Protocols: Create a way to define a protocol type. A protocol should be definable using TypeScript's type system.
- Type Checking: Implement a function that takes an object and a protocol type, and returns
trueif the object conforms to the protocol, andfalseotherwise. - Property and Method Checks: The checking mechanism should verify the presence and correct type of properties, as well as the presence and (optionally) signature of methods defined in the protocol.
Expected Behavior:
- A function, let's call it
conformsToProtocol, should take two arguments:obj(the object to check) andprotocol(the protocol type definition). - It should return
trueifobjhas all the properties and methods specified byprotocolwith matching types. - It should return
falseifobjis missing any required properties or methods, or if any of them have incorrect types.
Edge Cases to Consider:
- Protocols with optional properties.
- Protocols with methods that accept arguments.
- Protocols with methods that return values.
- Handling
nullorundefinedas input objects.
Examples
Example 1:
Input:
interface Printable {
print(): void;
}
const myObject = {
print: () => console.log("Hello!"),
data: 123
};
const anotherObject = {
data: 456
};
Output of conformsToProtocol(myObject, Printable): true
Output of conformsToProtocol(anotherObject, Printable): false
Explanation: myObject has a print method matching the Printable protocol. anotherObject is missing the print method.
Example 2:
interface User {
id: number;
name: string;
isActive?: boolean; // Optional property
getDisplayName(): string;
}
const validUser = {
id: 1,
name: "Alice",
isActive: true,
getDisplayName: function() { return this.name; }
};
const missingMethodUser = {
id: 2,
name: "Bob",
isActive: false
};
const wrongTypeUser = {
id: "3", // Wrong type for id
name: "Charlie",
getDisplayName: () => "Charlie"
};
const missingOptionalUser = {
id: 4,
name: "David",
getDisplayName: () => "David"
}
Output of conformsToProtocol(validUser, User): true
Output of conformsToProtocol(missingMethodUser, User): false
Output of conformsToProtocol(wrongTypeUser, User): false
Output of conformsToProtocol(missingOptionalUser, User): true
Explanation: validUser meets all requirements of the User protocol. missingMethodUser lacks getDisplayName. wrongTypeUser has an incorrect type for id. missingOptionalUser is valid because isActive is optional.
Example 3:
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
multiply?(a: number, b: number): number; // Optional method
}
const basicCalculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
const advancedCalculator = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
};
const incompleteCalculator = {
add: (a, b) => a + b,
// subtract is missing
multiply: (a, b) => a * b
};
Output of conformsToProtocol(basicCalculator, Calculator): true
Output of conformsToProtocol(advancedCalculator, Calculator): true
Output of conformsToProtocol(incompleteCalculator, Calculator): false
Explanation: basicCalculator has the required add and subtract methods. advancedCalculator has all methods, including the optional multiply. incompleteCalculator is missing the mandatory subtract method.
Constraints
- The
conformsToProtocolfunction should be purely runtime JavaScript code, but it will be used with TypeScript types. You will need to design your protocol definitions and checking logic in TypeScript. - The checking should be as robust as possible within the limits of runtime JavaScript and TypeScript's type system. You cannot perfectly replicate compile-time type checking at runtime, but aim to cover the common cases.
- Assume
objcan benullorundefined. The function should gracefully handle these inputs (likely returningfalse). - Performance is not a primary concern for this challenge, but avoid excessively inefficient algorithms.
Notes
- Consider how you will represent the "protocol type" at runtime for your checking function. You might use TypeScript's
interfaceortypealiases for definition, but you'll need a way to inspect these definitions or their expected structure at runtime. - Think about how to differentiate between properties and methods during your check.
- For methods, you'll need to check if the property exists and if it's a function. You might also consider if you need to check method signatures (though this can become very complex at runtime). For this challenge, simply checking if it's a function is sufficient for methods.
- The goal is to create a practical and understandable way to check for structural compatibility, similar to how duck typing works in other languages.