2017-07-29 14:46
本周主要的工作时间(摸鱼之余)都在折腾一个模块相关的问题。以下描述是尽量简化并换了一个应用场景后的版本,以便描述问题核心并且不暴露公司代码。
假设你的项目里有两个module,分别是order和payment。order依赖于payment,这样order下的类,比如结账流程,就可以使用支付相关的功能。但是后来某个新开发的功能需要在payment下的某个类中使用order下的类,比方说,你需要在某种信用卡的信息页面CardInfoView(payment负责的类)上提供“积分换好礼”入口,这个功能需要跳转到order模块负责的结账确认页面CheckoutView。CardInfoView根本看不到CheckoutView类,因为依赖是单向的。再假设这两个类都不能移动到别的模块。你怎么办?
(在这个虚拟的例子中,页面跳转需要获取某个View。这里所谓的View是指一个基本的功能单元。所有的View都继承自虚拟类BaseView。BaseView定义了页面跳转方法。)
可以想到的一个方法是在payment里定义一个CreditsRedeemListener接口,监听“积分换好礼”按钮点击。这个Listener的实例由上游某个同时依赖于order和payment的模块提供,比如根模块app,这样它就可以在事件发生时,生成CheckoutView并跳转。但是这样一来,“跳转到结账页面”这部分代码逻辑就不得不放在上游模块里。这样不利于维护。我们还是希望这部分代码能位于payment模块里。
我们用的办法是定义一个CheckoutViewProvider接口,它只有一个方法provideCheckoutView,返回BaseView实例。CheckoutViewProvider实例还是需要上游模块来提供。不过这个实例要做的仅是生成CheckoutView,而实现跳转的代码还在payment中。与Listener方案相比,Listener的逻辑是:当点击按钮时,交由流程上游的类决定怎么做;而Provider的逻辑是:当点击按钮时,跳转到Provider所提供的View去。后者逻辑更为明确一些。
这个接口可以定义在payment里。而我们出于另外的考虑,建一个order-payment-shared模块,让前两个模块都依赖于它。CheckoutViewProvider位于这个模块里。而它的实例通过依赖注入来提供到CardInfoView中。
实际上,这周我所做的事情更为复杂一些。由于历史原因,我们的项目一开始是payment依赖于order的,从CardInfoView到CheckoutView是直接跳转,而order流程获取payment方法则使用了上述方案,通过PaymentMethodsProvider来提供。我们发现这个结构无法支持正在开发的新功能,不得已调整模块依赖关系,引入了上述新的Provider接口。由于order-payment-shared模块的存在,调转模块的依赖关系并不困难。但是上述“在模块1中使用模块2里的接口获取由模块3提供的、定义于模块4中的类”这样的设计,毕竟留下了代码不够简洁的遗憾。
以上order、payment、CardInfoView、CheckoutView等例子均为虚构。