IDE(编辑器)报错
循环引用泛型是我起的名字,不知道官方的名字是什么。这个问题是我在定义Android 的MVP时提出来的。具体是什么样的呢?我们看一下我的基础的MVP定义:
interface IPresenter<V> { fun getView(): V
}interface IView<P> { fun getPresenter(): P
}
这里我定义了一个View和Presenter的接口,但是实际上这两个东西现在没什么关联。
这时,我想提供一个Presenter与View的基类,在基类中实现某些通用功能,并且增加这样的约束:Presenter中拿到的V必须是View的子类,View中拿到的P必须是Presenter的子类,那我们知道,可以通过kotlin,指定上界的方式。
我们修改上边的代码:改成下边这样:
interface IPresenter<V: IView> { fun getView(): V
}interface IView<P: IPresenter> { fun getPresenter(): P
}
同时我们也很容易发现,编译器报错了:
IDE提示这个错误:One type argument expected for interface IPresenter<V>
,意思是:IPresenter必须指定一个泛型类型才可以。在Kotlin中,这样的写法是直接保存的,在Java中,这种写法只是警告,所以Java中使用这种写法,“没问题”(没有IDE报错问题)。
Java 中如何实现这个定义
我们来看一下Java
interface IPresenter<V extends IView> { V getView();
} interface IView<P extends IPresenter> { P getPresenter();
}
接着我们看IDE的提示:
出现了警告,但是不报错了,我们看一下提示是什么:
Raw use of parameterized class 'IPresenter'
意思是其实和Kotlin提示的差不多,希望你指定一个泛型类型,而不是直接使用这个类。
在Java中,为了解决这个提示,可以使用?
。
interface IPresenter<V extends IView<?>> { V getView();
} interface IView<P extends IPresenter<?>> { P getPresenter();
}
当我们在后边的泛型中加入?
来规定一下泛型,编译器就不会提示错误和警告。
在Kotlin中如何实现类似的接口定义?
我们尝试在Kotlin的代码中实现增加上界的方式: 我们知道kotlin 的通配符 *
对应着java中的?
,那我们模仿一下Java的方式不就行了吗?
interface IView<P: IPresenter<*>> { fun getPresenter(): P
} interface IPresenter<V: IView<*>> { fun getView(): V
}
结果发现又报错了:
This type parameter violates the Finite Bound Restriction
然后我又尝试了很多方式都不行。
解决方案
1、利用Kotlin与Java混合开发
既然Java中是可以定义的,那么可以通过定义一个Java类型的接口不就行了。确实可以,但是当这个定义不是接口,而是抽象类时,你需要再Java代码中写一堆方法实现、属性定义,这时你就无法体验到Kotlin代码的优势了。
定义用Java,实现用Kotlin。
2、增加Self泛型
在StackOverFlow 找到了一个解决方案,就是增加一个泛型,
interface IView<Self: IView<Self, P>, P: IPresenter<P, Self>> { fun getPresenter(): P
} interface IPresenter<Self: IPresenter<Self,V>, V: IView<V, Self>> { fun getView(): V
}
我们通过增加一个泛型,并传递自己的方式,实现了循环引用泛型。
但是这样的代码,看起来定义变长了。
当然我们为了看起来更清晰,也可以改成kotlin的另一种写法:
interface IView<Self, P> where Self: IView<Self, P>, P: IPresenter<P, Self>{ fun getPresenter(): P
} interface IPresenter<Self, V> where Self: IPresenter<Self,V>, V: IView<V, Self>{ fun getView(): V
}
虽然变长了,但是定义看起来清晰了,缺点就是,增加多了一个泛型。
也可以把Self
放在后边
interface IView<P, Self> where P: IPresenter<Self, P>, Self: IView<P, Self> { fun getPresenter(): P
} interface IPresenter<V, Self> where Self: IPresenter<V, Self>, V: IView<Self, V>{ fun getView(): V
}
在使用时,我们只需要把Self指定为当前类即可。
class LoginView() : IView<LoginPresenter, LoginView> { override fun getPresenter(): LoginPresenter { TODO("Not yet implemented") }
} class LoginPresenter() : IPresenter<LoginView, LoginPresenter> { override fun getView(): LoginView { TODO("Not yet implemented") }
}
完整案例MVP
通常我们不会在接口的位置直接定义循环引用,更多的时候是在实现某一个方案时,发现有通用的功能,想把通用功能统一抽离在父类里边。
案例:登录功能,我现在有一个登录的功能,为了简单,此处只增加两种方式:手机号码&密码登录。手机号码&验证码登录。
首先看这个功能的通用逻辑部分:
1、手机号码验证。
2、登录成功之后的用户数据获取。
各自独立的部分:
密码登录:检查密码位数,用密码登录。
验证码登录:发送验证码,检查验证码,用验证码登录。
interface IView<P> { fun getPresenter(): P?
} interface IPresenter<V> { fun getView(): V?
} abstract class BaseView<P : BasePresenter<*>>() : IView<P> { private var myPresenter: P? = null override fun getPresenter(): P? { if (myPresenter == null) { myPresenter = createPresenter() myPresenter?.setView(this) } return myPresenter } fun showLoadingView() { // 具体的展示加载中动画的实现 } fun hideLoadingView() { // 具体的去除加载中动画的实现 } fun destroyView() { hideLoadingView() // 其他页面销毁时操作 } abstract fun createPresenter(): P?
} abstract class BasePresenter<V> : IPresenter<V> { protected var myView: V? = null override fun getView(): V? { return myView } fun setView(view: Any?) { this.myView = view as V? } } abstract class BaseLoginView<Presenter, Self> : BaseView<Presenter>() where Self : BaseLoginView<Presenter, Self>, Presenter : BaseLoginPresenter<Self, Presenter> { } abstract class BaseLoginPresenter<View, Self> : BasePresenter<View>() where View : BaseLoginView<Self, View>, Self : BaseLoginPresenter<View, Self> { fun checkPhone(phone: String): Boolean { // 检查手机号具体实现 return true } fun onObtainTokenSuccess(token: String) { // 登录成功具体操作 }
} class LoginCodeView : BaseLoginView<LoginCodePresenter, LoginCodeView>() { override fun createPresenter(): LoginCodePresenter { return LoginCodePresenter() } } class LoginCodePresenter : BaseLoginPresenter<LoginCodeView, LoginCodePresenter>() { fun checkCode(): Boolean { // 检查Code 的具体代码 return true }
} class LoginPasswordView : BaseLoginView<LoginPasswordPresenter, LoginPasswordView>() { override fun createPresenter(): LoginPasswordPresenter { return LoginPasswordPresenter() } } class LoginPasswordPresenter : BaseLoginPresenter<LoginPasswordView, LoginPasswordPresenter>() { fun checkCode(): Boolean { // 检查Code 的具体代码 return true }
}
参考链接
https://stackoverflow.com/questions/46682455/how-to-solve-violation-of-finite-bound-restriction-in-kotlin