zl程序教程

您现在的位置是:首页 >  前端

当前栏目

[Typescript] Builder pattern - 06 Exercise

typescript 06 pattern Builder
2023-09-14 08:59:11 时间
type TMethodListener<T> = (copyFn: T, ...args: any[]) => void;
type TBuildInfo<TOverriden> = {
  mutationList: TOverriden;
  error?: string;
};

/**
 * @description
 *
 * Overriden class helps to overriden object.
 *
 * Prevent override the methods which already been overriden;
 * This is not applied to props
 *
 * .method(methodName, callback: (originalFunction: Function, ...args: any[]) => void)
 * .prop(propName, callback: (prevVal) => newVal): overriden the prop with new value]
 * .build(callback?: (overridenList) => void): get final overriden object back,
 *    callback function get all the overriden props and methods array
 */
class Overriden<TMap extends object = {}, TOverriden extends string[] = []> {
  #map: TMap;
  #overriden = [] as any as TOverriden;
  #done = false;
  #error?: string;

  constructor(obj: TMap) {
    this.#map = obj;
  }

  build(callback?: (info: TBuildInfo<TOverriden>) => void) {
    this.#checkDone();
    this.#done = true;
    if (callback && typeof callback === "function") {
      callback({
        mutationList: this.#overriden,
        error: this.#error,
      });
    }
    return this.#map;
  }

  prop<TPropName extends keyof TMap & string, RT>(
    propName: TPropName extends TOverriden[number] ? never : TPropName,
    listener: (prevVal: TMap[TPropName]) => RT
  ): Overriden<Record<TPropName, RT> & TMap, [...TOverriden, TPropName]> {
    this.#checkDone();
    (this.#map as any)[propName] = listener(this.#map[propName]);
    this.#overriden.push(propName);
    return this as any;
  }

  method<TMethodName extends keyof TMap & string>(
    methodName: TMethodName extends TOverriden[number] ? never : TMethodName,
    listener: TMethodListener<TMap[TMethodName]>
  ): TMethodName extends TOverriden[number]
    ? Overriden<TMap, TOverriden>
    : Overriden<
        Record<TMethodName, TMethodListener<TMap[TMethodName]>> & TMap,
        [...TOverriden, TMethodName]
      > {
    this.#checkDone();
    if (typeof this.#map[methodName] !== "function") {
      this.#error = `${methodName} not a function`;
      throw new Error(this.#error);
    }
    if (this.#overriden.includes(methodName)) {
      this.#error = `${methodName} got overriden already`;
      console.warn(this.#error);
      return this as any;
    }
    const copyFn = this.#map[methodName];
    (this.#map as any)[methodName] = (...args: any[]) => {
      listener.call(this.#map, copyFn, ...args);
    };
    this.#overriden.push(methodName);
    return this as any;
  }

  #checkDone(): asserts this is this & { error: undefined } {
    if (this.#done) {
      this.#error =
        "There is already another overriden process for the same object";
      throw new Error(this.#error);
    }
  }
}

const win = {
  close() {
    console.log("close is called");
  },
  open(...args: any[]) {
    console.log("open is called", ...args);
    return args[0];
  },
  alert() {
    console.log("alert is called");
  },
  isObj: true,
  type: "window",
  info: {
    name: "name",
    age: 12,
  },
};

const overridenWin = new Overriden(win);

const chain = overridenWin
  .prop("info", (prevInfo) => {
    return { ...prevInfo, address: "new address" };
  })
  .method("open", (copyFn, ...args) => {
    copyFn(...args);
  });

// do something here

//
//

const res = chain.method("close", () => {}).build(console.log);
res.info.address;
res.open();