背景 模板,泛型,通配符的使用记录
下单示例 从一个下单的例子来记录下模板与通配符的使用
定义接口 下单一般流程为报文定义,报文核对,落表数据生成,落表,落表后操作等等这些,具体视业务情况而定,这里主要记录模板泛型通配符,后续就简化成只操作一个落表接口
报文接口 这里定义一个报文接口的目的是将不同渠道格式的报文定义一个统一的内容(比如直接返回部分初始化的数据表对象),这样实现类就不用依赖于具体的某个请求报文而是下单报文接口(依赖倒置)
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 35 36 37 38 39 40 41 42 public interface OrderRequest <T extends OrderInfo > { public T generateOrderInfo () ; } public class OrderInfo { protected BigDecimal amt; protected String serial; protected String rcvNo; public OrderInfo (BigDecimal amt, String serial, String rcvNo) { this .amt = amt; this .serial = serial; this .rcvNo = rcvNo; } public OrderInfo () { } @Override public String toString () { return "OrderInfo{" + "amt=" + amt + ", serial='" + serial + '\'' + ", rcvNo='" + rcvNo + '\'' + '}' ; } }
下单接口 这里简化一下,只定义落表接口。落表接口接收不同类型的要落表信息,但要落表信息都是基础落表信息(BaseSavedInfo.java)的子类,所以使用”extends SavedInfo”关键字去限制一下,具体的实现类则必须传入SavedInfo或者其子类。因此这里可以使用模板接口或者模板方法,定义方法见 ,这两种方法各有其特点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class BaseSavedInfo { protected OrderInfo orderInfo; public BaseSavedInfo (OrderInfo orderInfo) { this .orderInfo = orderInfo; } @Override public String toString () { return "SavedInfo{" + "orderInfo=" + orderInfo + '}' ; } }
模板方法接口Save 这里Save接口有一个模板方法save,他的方法模板类型限制在BaseSavedInfo及其子类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface Save { public <T extends BaseSavedInfo > void save (T savedInfo) ; }
模板接口SaveT 这里定义的为一个模板接口SaveT,模板范围限制在BaseSavedInfo及其子类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface SaveT <T extends BaseSavedInfo > { public void save (T savedInfo) ; }
下单行为抽象类 这里设置一个下单行为的抽象类目的是为了将下单流程组装起来,因为涉及到Save接口作为依赖,所以接口不适用需要换为抽象类。这里抽象类的定义可以分为三种 。
使用Save接口的抽象下单类:方式一 1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class AbstractOrder <T extends OrderRequest <?>> { protected Save save; public AbstractOrder (Save save) { this .save = save; } public abstract void order (T orderRequest) ; }
这里的”<T extends OrderRequest<?>” 表示AbstractOrder是一个模板类,且其实现类的模板类型限制在OrderRequest接口,这样做的好处是所有下单虚函数的实现类都实现了OrderRequest接口,从而统一下单行为,满足同一个标准(当然也可以不指定),后面的’?’表示任意通配符,也就是OrderRequest接口的泛型这里不关心
AbstractOrder抽象下单类使用Save接口作为其依赖因此直接定义Save类型的成员变量即可。如果是SaveT类型则需要特别处理
“public abstract void order(T orderRequest);” 表示这是一个抽象下单接口,接收一个模板类型参数,也就是实现了OrderRequest接口的请求报文体。该报文体根据下单渠道自行定义
使用SaveT接口的抽象下单类: 方式二 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class AbstractOrderTR <T extends OrderRequest <?>, R extends BaseSavedInfo > { protected SaveT<R> save; public AbstractOrderTR (SaveT<R> save) { this .save = save; } public abstract void order (T orderRequest) ; }
这里AbstractOrderTR抽象下单类使用SaveT接口作为其依赖,而且在类上通过模板”R extends BaseSavedInfo”指定SaveT模板类型为BaseSavedInfo及其子类
使用SaveT接口的抽象下单类: 方式三 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class AbstractOrderT <T extends OrderRequest <?>> { protected SaveT<? extends BaseSavedInfo > save; public AbstractOrderT (SaveT<? extends BaseSavedInfo> save) { this .save = save; } public abstract void order (T orderRequest) ; }
这里AbstractOrderTR抽象下单类使用SaveT接口作为其依赖,但不像方式二通过模板来限制SaveT模板类型,而是使用SaveT<? extends BaseSavedInfo>通配符,来限制该模板类型仅为BaseSavedInfo及其子类,当然也可以不限制仅用SaveT<?>和SaveT。但是SaveT和(SaveT<?>,SaveT<? extends BaseSavedInfo>)不一样,SaveT并没有使用通配符,它更类似于方式一的用法,只是一个基类。而SaveT<?>和SaveT<? extends BaseSavedInfo>因为都作为通配符使用,所以在AbstractOrderTR的实现类AbstractOrderTRImp 中不能直接使用,因为编译器并不能确定此处SaveT的具体类型,详见
定义实现 这里用外部渠道下单和内部渠道下单来模拟实现。一些实体类定义不具体展示,详细可在这里查看完整包
内部渠道下单实现类 InnerOrder类继承了抽象下单类AbstractOrder(方式一 ),并指定了模板类型为InnerOrderRequest(内部请求报文)
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class InnerOrder extends AbstractOrder <InnerOrderRequest> { public InnerOrder (InnerSaveOrder save) { super (save); } @Override public void order (InnerOrderRequest innerOrderRequest) { try { save.save(new InnerBaseSavedInfo (innerOrderRequest.generateOrderInfo(), "this is inner special message" )); } catch (Exception e) { throw new RuntimeException (e); } } } public class InnerSaveOrder implements Save { @Override public <T extends BaseSavedInfo > void save (T savedInfo) { System.out.println("内部渠道下单信息开始落表" ); System.out.println(savedInfo.toString()); } }
从代码里面可以看到Save接口采用模板方法这种方式有个麻烦的地方在于实现类中因为是重写save方法所以也必须是模板方法,虽然这时已经明确知道参数类型为BaseSavedInfo的实现类InnerSavedInfo,但是在方法中无法直接获取其特定属性(比如获取InnerSavedInfo子类独有的字段),暂时没明白除了强制类型转换外该怎么解决
外部渠道下单实现类(AbstractOrderTR) OuterOrderTR继承了下单虚函数AbstractOrderTR(方式二 ),并指定了AbstractOrderTR的两个模板为OuterOrderRequest和OuterBaseSavedInfo,从而明确指定了抽象类的SaveT这个成员函数的模板类型为OuterBaseSavedInfo
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 public class OuterOrderTR extends AbstractOrderTR <OuterOrderRequest, OuterBaseSavedInfo> { public OuterOrderTR (OuterSaveOrderImp save) { super (save); } @Override public void order (OuterOrderRequest outerOrderRequest) { try { save.save(new OuterBaseSavedInfo (outerOrderRequest.generateOrderInfo())); } catch (Exception e) { throw new RuntimeException (e); } } }
外部渠道下单实现类(AbstractOrderT) 这里OuterOrderT继承了AbstractOrderT虚函数方式三 ,成员函数SaveT<? extends BaseSavedInfo> save只通过上界通配符限定,我一开始觉得编译器能通过传入的OuterBaseSavedInfo类对象来初始化SaveT对象时推断出SaveT的模板类型为OuterBaseSavedInfo而不是InnerBaseSavedInfo(BaseSavedInfo的另一个实现类),但实际上编译器好像无法推测出save的类型导致SaveT对象的public void save(T savedInfo)方法无法接收任何参数(除了null),这里和List<? extends Number> list无法list.add(1)问题很像 ,调用save方法时会导致类型安全问题,无法编译通过。
相关解释
解决办法为明确指定SaveT模板类型
1 protected SaveT<OuterBaseSavedInfo> save;
类定义如下
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 public class OuterOrderT extends AbstractOrderT <OuterOrderRequest> { protected SaveT<OuterBaseSavedInfo> save; public OuterOrderT (OuterSaveOrderImp save) { super (save); this .save = save; } @Override public void order (OuterOrderRequest outerOrderRequest) { try { save.save(new OuterBaseSavedInfo (outerOrderRequest.generateOrderInfo())); } catch (Exception e) { throw new RuntimeException (e); } } }
总结
上面的三个实现类大概展示了通配符与模板使用的示例,以及一些设计原则(依赖倒置,接口隔离,单一,开闭)的使用,以及其中的一些坑,建议大家结合具体的示例或实际的业务来理解。
对于上界和下界通配符,个人感觉<? extends ClassA>这种用得多一点,这种相当于指定了一个规范ClassA,后续传入的参数都要实现这个规范ClassA或者继承ClassA。至于上下界的定义为什么那么叫,个人感觉没那么重要
完整代码见:
简略版
详细版