本文实现基于C#与Typescript.
链式调用
使用Generics 实现链式调用扩展
使用local class优化代码结构
链式调用 链式调用方法是一个常见的编程范式,特别是在对象构造器的类上,通常是一些builder
的类使用了链式调用。链式调用给复杂对象的创建参数提供了简便的调用方式。
一个常见的Method Chaining 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Data{ public items:Item[] = []; public num:number = 0 ; } class DataBuilder{ protected m_data:Data = new Data(); public static create():DataBuilder{ return new DataBuilder(); } public addItem(o:Item):DataBuilder{ this .m_data.items.push(o); return this ; } public addNum(n:number ):DataBuilder{ this .m_data.num +=n; return this ; } public finish():Data{ return this .m_data; } } let data = DataBuilder.create().addItem(new Item()).addNum(100 ).addItem(new Item()).finish();
考虑继承的情况,比如DataBuilder
是在提供的一个Library中的类,同时希望使用的开发者可以对这个链式调用构造类进行扩展。
具体的情形就变为:
1 2 3 4 5 6 7 8 9 10 class CustomDataBuilder extends DataBuilder{ public static create():CustomDataBuilder{ return new CustomDataBuilder(); } public static addCustomItem(o:Item):CustomDataBuilder{ this .m_data.items.push(o); } } let data = CustomDataBuilder.create().addCustomItem(null ).addNum(100 ).finish();
这样我们可以使用子类的方法进行链式调用,同时也可以调用到基类的addNum
方法。
但是这里存在一个问题,当CustomDataBuilder
的对象调用了其基类的方法后,返回的是基类DataBuilder
的实例,就无法再次链式调用CustomDataBuilder
的方法了。也就是:
1 let data = CustomDataBuilder.create().addNum(100 ).addCustomItem(null );
虽然typescript会被编译成js,this
依旧是CustomDataBuilder
的对象可以正常执行,但是typescript的类型检测却无法通过。 同时这个pattern在C#中也是无法完成编译的。
使用Generics实现链式调用扩展 所以我们希望所有的链式调用方法返回的都是其本身的类型签名,这样我们就必须用到GenericType。
1 2 3 4 5 6 class ABuilder<T extends ABuilder<T>>{ public funcA():T{ return ; } }
由于链式调用的需求,T
必须是ABuilder<T>
的子类。所以T
带有T extends ABuilder<T>
的约束。
同时由于我们没法限定ABuilder<T>
是T
或其子类,所以funcA()
中不能return this
,所以我们引入一个T
的成员。
1 2 3 4 5 6 class ABuilder<T extends ABuilder<T>>{ protected t:T; public funcA():T{ return this .t; } }
由于ABuilder
是泛型类,不能直接创建其对象,在typescript中let b = new ABuilder()
时,泛型类T
是一个空对象{}
。 我们需要引入一个额外的类,来实现其泛型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ABuilder<T extends ABuilder<T>>{ protected t:T; public funcA():T{ return this .t; } } class A extends ABuilder<A>{ public static create():A{ let a = new A(); a.t = a; return a; } } let a = A.create().funcA().funcA().funcA();
实现继承
1 2 3 4 5 6 7 8 9 10 11 12 13 class B extends ABuilder<B>{ public static create():B{ let b = new B(); b.t = b; return b; } public funcB():B{ return this .t; } } let a = A.create().funcA().funcA().funcA();let b = B.create().funcB().funcA().funcB();
这样我们就可以通过类的继承来扩展ABuilder
。
如果类B
也需要被扩展,那么我们可以定义一个BBuilder<T>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class BBuilder<T extends BBuilder<T>> extends ABuilder<T>{ public funcB():B{ return this .t; } } class B extends BBuilder<B>{ public static create():B{ let b = new B(); b.t = b; return b; } } class C extends BBuilder<C>{ public static create():C{ let c = new C(); c.t = c; return c; } public funcC():C{ return this .t; } } let c = C.create().funcA().funcB().funcA();
使用local class优化代码结构 由于在C#中,可以使用相同的Symbol定义一个类和一个类的范型类,这样将原先的一个类拆封为两个结构上看上去依旧优雅。
1 2 3 4 5 6 public class A<T> where T:A<T>{ } public class A: A<A>{ }
要进行扩展时使用A<T>
,直接调用时使用A
。
但是在TS中泛型仅仅是Typescript Compiler的语法糖,最终编译为的JS中A<T>
与A
共用了同一个域中的名称。所以就一定要有两个不同的名称。
我们可以使用typescript
中的local class来将两个类合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class A<T extends A<T>>{ protected t:T; public static ABuilder = class ABuilder extends A<ABuilder>{ private constructor ( ){ super (); this .t = this ; } public static create():ABuilder{ return new ABuilder(); } } public funcA():T{ return this .t; } } class B<T extends B<T>> extends A<T>{ protected t:T; public static BBuilder = class BBuilder extends B<BBuilder>{ private constructor ( ){ super (); this .t = this ; } public static create():BBuilder{ return new BBuilder(); } } public funcB():T{ return this .t; } } let a = A.ABuilder.create().funcA().funcA().funcA();let b = B.BBuilder.create().funcB().funcA().funcB();
Gist - C#/Typescript