Skip to content

Instantly share code, notes, and snippets.

@asleepace
Last active January 30, 2023 23:16
Show Gist options
  • Save asleepace/2ad20559c31379ffc0e999bee4467ac2 to your computer and use it in GitHub Desktop.
Save asleepace/2ad20559c31379ffc0e999bee4467ac2 to your computer and use it in GitHub Desktop.
/**
* 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