关于抽象,是面向对象的特性,之前实际写代码的时候,可能注意得不是很好,对于这个点的理解也不是很到位。
但是因为公司项目涉及到了组件化,不同组件之间的通信其实是依赖抽象接口来规范的,或者说是约束的。
而一开始,我在定义抽象接口的时候,就处理得不是很好,偶尔会把具体业务层的实现带到接口定义之中,虽然觉得这样有点不对劲,但是却无法参透。
后来,在 code review 的时候,我的导师针对我的问题跟我聊了聊,在受到点拨之后,我感觉我对此的认知有了些许进步。
说得简单的,就是:
抽象层不应该对外暴露具体的实现参数,即不是面向实现编程,而要面向抽象编程。
因为对于抽象层来说,并不应该关心具体的实现,只需要做到 “定义”,至于业务层具体怎么实现,那是业务的事,在外人看来,只需要根据抽象层对外暴露的对应的接口方法能够实现目的即可。
而且对于抽象层来说,要做到替换业务层的实现,对外是无感知的,但是如果抽象层参杂了业务层的东西,那还怎么做到替换业务层的时候对抽象层不产生影响。
比如我对外定义一个抽象接口 IPageService
,里面有个抽象方法 toUserInfoPage()
,用于表示跳转到应用的用户信息页。
但是,在具体的实现中,假设有两种情况,一种是用户信息页实际上在首页的一个 tab 项,假设是第 X 个 tab,那跳转到个人信息页此时就对应跳转到首页第 X 个 tab 的逻辑;第二种则是用户信息页是一个二级页面,那此时的逻辑则是跳转到对应的 Activity。
此时,如果抽象层没有定义好,针对第一种情况,则可能会将形参 Int index
也带入到 IPageService#toUserInfoPage()
方法中,从而变成,fun toUserInfoPage(index: Int)
,用来在实现类 IPageImpl
中通过参数 index
来指定跳转到首页第 X 个 tab(即个人信息页)。
interface IPageService {
fun toUserInfoPage(index: Int)
}
class PageFirstImpl: IPageService {
fun toUserInfoPage(index: Int) {
// use index jump to user info tab
}
}
这种情况下,其实就是对外暴露了业务层的实现,而且调用的时候必须要指定 index
为 X 才能达到目的。
val pageService = PageFirstImpl()
// should introduce X, and should know index be X
pageService.toUserInfoPage(X)
之后,如果要该成第二种实现,此时参数 index
就相当于摆设了,不会有丝毫用处。
class PageSecondImpl: IPageService {
fun toUserInfoPage(index: Int) {
// start UserInfoActivity, index is useless
}
}
而正确的实现应该是像下面那样,在具体的实现类中直接指定 X 即可,因为外部调用时根本是不需要知道的,只要能够实现跳转到用户信息页即可。
interface IPageService {
fun toUserInfoPage()
}
class PageFirstImpl: IPageService {
fun toUserInfoPage() {
val index = X
// jump to user info tab via local variable ‘index’
}
}
再补充一点,即不要本末倒置地先去想到业务层应该怎么实现,再根据实现去定义抽象层的接口方法,这个时候就会被业务层的参数给影响到。