泛型+春天:愿力量与您同在

很久很久以前,在一个遥远的银行...


美好的一天,哈伯。 今天,终于,我的手再次到达这里写字。 但是,与以前的教程(今天的文章)不同的是,我想分享我的经验,并展示这种机制(如泛型)的力量,这种机制与Spring Magic一起变得更加强大。 我想立即警告您,为了理解本文,您需要了解快速入门的基本知识,并且对泛型有更多的想法,而不仅仅是“泛型,我们在ArrayList中用引号表示”。

第1集:


首先,在工作中,我的任务大致是这样的:大量的汇款和一定数量的共同领域。 另外,每个翻译对应于类-请求从一个状态转移到另一种状态并重定向到另一种api。 因此,有一些从事转换的建筑商。

我仅通过继承就解决了通用字段的问题。 所以我上了课:

public class Transfer { private TransferType transferType; ... } public enum TransferType { INTERNAL, SWIFT, ...; } public class InternalTransfer extends Transfer { ... } public class BaseRequest { ... } public class InternalRequest extends BaseRequest { ... } ... 

第2集:


然后是控制器的问题-它们都必须具有相同的方法-checkTransfer,approveTransfer等。 在这里,第一次但不是最后一次,泛型对我很有用:我用必要的方法制成了一个通用控制器,并从中继承了其余的方法:

  @AllArgsConstructor public class TransferController<T extends Transfer> { private final TransferService<T> service; public CheckResponse checkTransfer(@RequestBody @Valid T payment) { return service.checkTransfer(payment); } ... } public class InternalTransferController extends TransferController<InternalTransfer> { public InternalTransferController(TransferService<InternalTransfer> service) { super(service); } } 

好吧,实际上是该服务:

 public interface TransferService<T extends Transfer> { CheckResponse checkTransfer(T payment); ApproveResponse approveTransfer(T payment); ... } 

这样,复制粘贴问题就减少到只调用超构造函数,而在服务中我们通常会丢失它。

但是!

第3集:


服务内部仍然存在问题:
根据转移的类型,必须调用各种构建器:

 RequestBuilder builder; switch (type) { case INTERNAL: { builder = beanFactory.getBean(InternalRequestBuilder.class); break; } case SWIFT: { builder = beanFactory.getBean(SwiftRequestBuilder.class); break; } default: { log.info("Unknown payment type"); throw new UnknownPaymentTypeException(); } } 

通用生成器接口:

 public interface RequestBuilder<T extends BaseRequest, U extends Transfer> { T createRequest(U transfer); } 

这里提出了用于优化的工厂方法,因此,switch / case处于单独的类中。 它看起来似乎更好,但是问题仍然存在-添加新翻译时,您必须修改代码,而且笨拙的开关/大小写不适合我。

第4集:


出路是什么? 最初,我想到通过类的名称来确定翻译的类型,并使用反射来调用所需的生成器,这将迫使将与项目一起工作的开发人员满足其类名称的某些要求。 但是有一个更好的解决方案。 想知道了,您可以得出结论,应用程序的业务逻辑的主要方面是翻译本身。 也就是说,如果没有,就没有其他。 那么为什么不把它们都绑起来呢? 稍微修改一下我们的类就足够了。 仿制药再次来了。

要求类别:

 public class BaseRequest<T extends Transfer> { ... } public class InternalRequest extends BaseRequest<InternalTransfer> { ... } 

和构建器界面:

 public interface RequestBuilder<T extends Transfer> { BaseRequest<T> createRequest(T transfer); } 

在这里,它变得越来越有趣。 我们面临的泛型特性几乎从未在任何地方提到过,并且主要在框架和库中使用。 实际上,作为BaseRequest,我们可以替换其继承人,该继承人对应于类型T,即:

 public class InternalRequestBuilder implements RequestBuilder<InternalTransfer> { @Override public InternalRequest createRequest(InternalTransfer transfer) { return InternalRequest.builder() ... .build(); } } 

目前,我们已经在我们的应用程序体系结构方面取得了很大的进步。 但是开关/外壳问题尚未解决。 还是...?

第5集:


这就是春天的魔术发挥作用的地方。

事实是,我们有机会使用getBeanNamesForType(ResolvableType类型)方法获取与所需类型相对应的bin名称数组。 而且ResolvableType类具有用于ClassWithGenerics(Class <?> Clazz,Class <?> ... Generics)的静态方法,您需要在其中传递类(接口)作为第一个参数,该方法将第二个参数用作泛型并返回相应的类型。 T e:

 ResolvableType type = ResolvableType.forClassWithGenerics(RequestBuilder.class, transfer.getClass()); 

返回以下内容:

 RequestBuilder<InternalTransfer> 

现在,还有一点魔力-事实是,如果您使用接口作为通用接口自动连接工作表,则其所有实现都将包含在其中:

 private final List<RequestBuilder<T>> builders; 


我们只需要仔细检查一下,然后使用实例检查找到合适的方法即可:

 builders.stream() .filter(b -> type.isInstance(b)) .findFirst() .get(); 


与该选项类似,仍然有机会自动装配ApplicationContext或BeanFactory,并在它们上调用getBeanNamesForType()方法以将我们的类型作为参数传递到那里。 但这被认为是不良品味的标志,并且不需要这种体系结构(特别感谢zolt85的评论)。
因此,我们的工厂方法采用以下形式:

  @Component @AllArgsConstructor public class RequestBuildersFactory<T extends Transfer> { private final List<RequestBuilder<T>> builders; public BaseRequest<T> transferToRequest(T transfer) { ResolvableType type = ResolvableType.forClassWithGenerics(RequestBuilder.class, transfer.getClass()); RequestBuilder<T> builder = builders.stream() .filter(b -> type.isInstance(b)) .findFirst() .get(); return builder.createRequest(transfer, stage); } } 

第6集:结论


因此,我们拥有一个具有深思熟虑的体系结构的微型框架,该框架要求所有开发人员都必须遵守它。 而且重要的是,我们摆脱了繁琐的切换/案例,添加新的翻译不会以任何方式影响现有的类,这是个好消息。

PS:
本文不鼓励在任何可能和不可能的地方使用泛型,但是在它的帮助下,我想分享它们允许您创建哪些强大的机制和体系结构。

致谢:
特别感谢Sultansoy ,如果没有Sultansoy ,就不会想到这种体系结构,并且很可能不会出现本文。

参考文献:
Github源代码

Source: https://habr.com/ru/post/zh-CN423655/


All Articles