mixin
azu opened this issue · comments
azu commented
export type Constructor<T> = new (...args: any[]) => T;
/*
static mixin<T extends object, U extends object, M1 extends object, I1 extends object>(
this: U & Constructor<T>,
m1: (superClass: U & Constructor<T>) => MixinClass<M1, I1>
): M1 & U & Constructor<T & I1>;
static mixin<T extends object,
U extends object,
M1 extends object,
I1 extends object,
M2 extends object,
I2 extends object>(
this: U & Constructor<T>, m1: (superClass: U & Constructor<T>) => MixinClass<M1, I1>, m2: (superClass: U & Constructor<T>) => MixinClass<M2, I2>): M1 & M2 & U & Constructor<T & I1 & I2>;
static mixin<T extends object,
U extends object,
M1 extends object,
I1 extends object,
M2 extends object,
I2 extends object,
M3 extends object,
I3 extends object>(
this: U & Constructor<T>,
m1: (superClass: U & Constructor<T>) => MixinClass<M1, I1>,
m2: (superClass: U & Constructor<T>) => MixinClass<M2, I2>,
m3: (superClass: U & Constructor<T>) => MixinClass<M3, I3>
): M1 & M2 & M3 & U & Constructor<T & I1 & I2 & I3>;
static mixin(...mixins: ((superClass: any) => MixinClass<any, any>)[]) {
// FIXME: workaround for https://github.com/Microsoft/TypeScript/issues/4130
const mixinBuilder = new MixinBuilder(this);
return (mixinBuilder as any).with(...mixins);
}
*/
export function mixin<T extends object, U extends object, M1 extends object, I1 extends object>(
superclass: U & Constructor<T>,
m1: MixinClass<M1, I1>
): M1 & U & Constructor<T & I1>;
export function mixin<T extends object,
U extends object,
M1 extends object,
I1 extends object,
M2 extends object,
I2 extends object>(
superclass: U & Constructor<T>, m1: MixinClass<M1, I1>, m2: MixinClass<M2, I2>): M1 & M2 & U & Constructor<T & I1 & I2>;
export function mixin<T extends object,
U extends object,
M1 extends object,
I1 extends object,
M2 extends object,
I2 extends object,
M3 extends object,
I3 extends object>(
superclass: U & Constructor<T>,
m1: MixinClass<M1, I1>,
m2: MixinClass<M2, I2>,
m3: MixinClass<M3, I3>
): M1 & M2 & M3 & U & Constructor<T & I1 & I2 & I3>;
export function mixin<T extends object, U extends object>(
superclass: U & Constructor<T>,
...mixins: MixinClass<any, any>[]
) {
return mixins.reduce((c, mixin) => mixin(c), superclass) as Constructor<T>
}
export type Properties<T> = { [K in keyof T]: T[K] };
export type MixinClass<T, P> = Properties<T> & Constructor<P>;
export class MixinBuilder<T extends object, U extends object> {
private superclass: Constructor<T> & U;
constructor(superclass: Constructor<T> & U) {
this.superclass = superclass;
}
with<M1 extends object, I1 extends object>(): Constructor<T & I1>;
with<M1 extends object, I1 extends object>(m1: MixinClass<M1, I1>): M1 & U & Constructor<T & I1>;
with<M1 extends object, I1 extends object, M2 extends object, I2 extends object>(
m1: MixinClass<M1, I1>,
m2: MixinClass<M2, I2>
): M1 & M2 & U & Constructor<T & I1 & I2>;
with<M1 extends object,
I1 extends object,
M2 extends object,
I2 extends object,
M3 extends object,
I3 extends object>(
m1: MixinClass<M1, I1>,
m2: MixinClass<M2, I2>,
m3: MixinClass<M3, I3>
): M1 & M2 & M3 & U & Constructor<T & I1 & I2 & I3>;
with(...mixins: Function[]): Constructor<T> {
return mixins.reduce((c, mixin) => mixin(c), this.superclass) as Constructor<T>;
}
}
azu commented
Copy
/**
*
* K is type of a key
* T[K] is type of its value
*/
export type PartialMap<T> = { [K in keyof T]?: (prev: T[K]) => T[K] };
/**
* Constructor type
*/
export type Constructor<T = {}> = new (...args: any[]) => T;
/**
* Copyable can copy current instance and map new value
*/
export interface CopyableMethod<T> {
copy(partial: Partial<T>): T;
mapCopy(partial: PartialMap<T>): T;
}
export type Properties<T> = { [K in keyof T]: T[K] };
/**
* Mixin Copyable to the BaseClass
* @param BaseClass
* @returns Copyable class
* @example
*
* class A extends Copyable<Entity<EntityIdentifier>>{}
*/
export const Copyable = <T extends Constructor>(BaseClass: T) => {
return class extends BaseClass {
/**
* Return partial change of this object
*
* e.g)
* `new Person("jack", 2).copy({age: 10})` is `new Person("jack", 10)`
*
*/
copy(partial: Partial<Properties<this>>): this {
const Prototype = Object.getPrototypeOf(this);
const newInstance = Object.create(Prototype);
return Object.assign(newInstance, partial);
}
/**
* Return partial change of this object by using functions
*
* e.g)
* `new Person("jack", 10).mapCopy({age: prev => prev+1})` is `new Person("jack", 11)`
*
* @param {PartialMap<T>} partial
* @returns {T}
*/
mapCopy(partial: PartialMap<this>): this {
const Prototype = Object.getPrototypeOf(this);
const newInstance = Object.create(Prototype);
const oldInstance: { [index: string]: any } = this;
for (const key of Object.keys(this)) {
if (key in partial) {
newInstance[key] = (partial as any)[key](oldInstance[key]);
} else {
newInstance[key] = oldInstance[key];
}
}
return newInstance;
}
};
};
azu commented
Note: mixin should support ValueObject
class AbstractBookmark extends ValueObject<BookmarkProps>{}
export class Bookmark extends Copyable(AbstractBookmark) {
}