Last active
January 30, 2023 23:16
-
-
Save asleepace/2ad20559c31379ffc0e999bee4467ac2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* GenericSubClass | |
* | |
* This class extends a base class (in this case FooType) without having to manually | |
* define all of the base classes properties and methods. This is accomplished using | |
* a Proxy wrapper and a factory method. | |
* | |
* When creating a new instance do not call new GenericSubClass directly, but instead | |
* use the static factory method GenericSubClass.new({ ... }) which will return the | |
* GenericSubClass with a proxy wrapper. | |
* | |
* When getting properties the proxied subclass will first search the GenericSubClass | |
* and if nothing is found, then will search the _parent (in this case FooType). | |
* | |
* When setting properties the proxied subclass will first check if the property | |
* exists on GenericSubClass, if it exists that will be the property that is set, | |
* otherwise it will check the _parent and try to set that. | |
* | |
* Example usage below. | |
*/ | |
interface ProxySubClass<ProxyBaseClass> { | |
/** store the base class in the _parent property */ | |
_parent: ProxyBaseClass | |
/** example method which extends the base class */ | |
exampleBaseMethod(): string | |
} | |
class GenericSubClass implements ProxySubClass<FooType> { | |
_parent: FooType | |
constructor(data: FooType) { | |
this._parent = data | |
} | |
// class method which returns a new instance of MyClass wrapped in a proxy | |
// that will get & set properties directly to the data property, then we | |
// return the proxied class casted to both MyClass & FooType | |
static new(data: FooType) { | |
// create a new instance of the class | |
const newClassInstance = new GenericSubClass(data) | |
// wrap new instance with a proxy to access properties on parent, when | |
// accessing a property is will check the instnace first, then the | |
// parent to allow override. | |
const newProxyInstance = new Proxy(newClassInstance, { | |
get(target, prop) { | |
if ((target as any)[prop]) return (target as any)[prop] | |
if ((target._parent as any)[prop]) return (target._parent as any)[prop] | |
}, | |
set(target, prop, newValue) { | |
if ((target as any)[prop]) { | |
(target as any)[prop] = newValue | |
return true | |
} | |
if ((target._parent as any)[prop]) { | |
(target._parent as any)[prop] = newValue | |
return true | |
} | |
return false | |
} | |
}) | |
// return wrapped proxy as union of class and FooType | |
return newProxyInstance as ProxySubClass<FooType> & FooType | |
} | |
exampleBaseMethod() { | |
return this._parent.name | |
} | |
} | |
// Exmaple Usage | |
// Here we have our FooType which we want to extend. | |
interface FooType { | |
name: string | |
greet(): void | |
} | |
const myFooType: FooType = { | |
name: 'Alice', | |
greet: () => { | |
console.log(`Hello, ${myFooType.name}`) | |
} | |
} | |
// Instantiating | |
// We create a new instance of GenericSubClass with the | |
// base class set to FooType | |
const test = GenericSubClass.new(myFooType) | |
// Getters | |
console.log(test) | |
console.log(test.name) | |
test.greet() | |
// Setters | |
test.name = "Bob" | |
console.log(test.name) | |
test.greet() | |
console.log(test.exampleBaseMethod()) | |
test.exampleBaseMethod = () => 'another one' | |
console.log(test.exampleBaseMethod()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment