Builder pattern using TypeScript interfaces

后端 未结 3 1018
慢半拍i
慢半拍i 2020-12-18 08:39

I would like to do something like this:

interface IPoint {
    x : number;
    y : number;
    z? : number;
}
const diag : IPoint = IPoint.x(1)
                      


        
相关标签:
3条回答
  • 2020-12-18 08:48

    Based on the previous answers I wrote a generic Typescript builder which provides:

    • Type-Safety
    • Differentiation of optional and required properties
    • Generic method (with) for adding parts of the object
    • Validation of the object before creating it
    • Domain Driven Design conformity (no need to specify methods when using Builder)

    If you are interested, you can find further information and examples here: https://github.com/hanterlantant/ts-generic-builder And the npm package here: https://www.npmjs.com/package/ts-generic-builder

    0 讨论(0)
  • 2020-12-18 08:59

    This handles the type:

    interface IPoint {
        x: number;
        y: number;
        z?: number;
    }
    
    type IBuilder<T> = {
        [k in keyof T]: (arg: T[k]) => IBuilder<T>
    } & { build(): T }
    
    
    let builder = {} as IBuilder<IPoint>
    
    const diag = builder.x(1).y(2).z(undefined).build()
    

    But I don't know how will you create the actual Builder thou. :)

    You can play around with it at the playground

    EDIT: Vincent Peng has created a builder-pattern npm package our of this (as mentioned in the comment). Go and give it some love!

    0 讨论(0)
  • 2020-12-18 09:01

    The following design adds type-safety by accomplishing 3 things:

    1. It is aware which of the required properties have been already provided.
    2. It is aware which of the optional properties have been already provided.
    3. Will only let you build once you have provided all required properties.

    The Point itself:

    interface Point {
      x: number;
      y: number;
      z?: number;
    }
    
    class Point implements Point {
      constructor(point: Point) {
        Object.assign(this, point);
      }
    }
    

    The Point builder:

    class PointBuilder implements Partial<Point> {
      x?: number;
      y?: number;
      z?: number;
    
      withX(value: number): this & Pick<Point, 'x'> {
        return Object.assign(this, { x: value });
      }
    
      withY(value: number): this & Pick<Point, 'y'> {
        return Object.assign(this, { y: value });
      }
    
      withZ(value: number): this & Required<Pick<Point, 'z'>> {
        return Object.assign(this, { z: value });
      }
    
      build(this: Point) {
        return new Point(this);
      }
    }
    

    Usage:

    /**
     * The `z` property is optional.
     */
    new PointBuilder()
      .withX(1)
      .withY(1)
      .build();
    
    /**
     * The `.build()` method cannot be called — we are still missing `y`.
     */
    new PointBuilder()
      .withX(1)
      .withZ(1);
    
    /**
     * The `z` property is correctly recognized as `number` (as opposed to `number | undefined`).
     */
    new PointBuilder()
      .withX(1)
      .withZ(1)
      .z
    
    0 讨论(0)
提交回复
热议问题