面试题答案
一键面试确保代码复用与隔离,避免过度耦合的方法
- 使用接口与抽象类:定义通用的接口或抽象类,不同模块实现这些接口或继承抽象类。这样在复用代码时,通过接口进行交互,实现模块间解耦。例如,定义一个数据获取接口
DataFetcher
,不同模块可以根据自身需求实现该接口,以获取特定的数据。
interface DataFetcher {
fun fetchData(): String
}
class ModuleADataFetcher : DataFetcher {
override fun fetchData(): String {
return "Data from Module A"
}
}
class ModuleBDataFetcher : DataFetcher {
override fun fetchData(): String {
return "Data from Module B"
}
}
- 依赖注入:使用依赖注入框架(如 Koin 或 Dagger)来管理模块间的依赖关系。通过依赖注入,模块只依赖于接口而不是具体实现,降低耦合度。例如,在一个视图模块中,通过依赖注入获取数据获取接口的实现。
class ViewModule(private val dataFetcher: DataFetcher) {
fun displayData() {
val data = dataFetcher.fetchData()
println("Displaying data: $data")
}
}
- 模块分层:将项目分为不同的层次,如数据层、业务逻辑层、表示层等。每层有明确的职责,不同模块在相同层次进行交互,减少跨层耦合。例如,数据层模块负责数据的获取与存储,业务逻辑层模块处理业务规则,通过接口进行交互。
模块接口设计与内部实现封装的理解
- 模块接口设计:
- 明确职责:接口应清晰定义模块对外提供的功能,避免功能过于庞杂。例如,文件操作模块的接口应专注于文件的读写、创建、删除等操作,而不是混入其他无关功能。
- 稳定性:接口一旦确定,应尽量保持稳定,避免频繁修改。因为接口的变化会影响依赖该接口的其他模块。
- 易用性:接口设计应易于理解和使用,参数和返回值应符合逻辑,便于其他模块调用。
- 内部实现封装:
- 信息隐藏:模块内部的实现细节应被封装,不对外暴露。其他模块只需要关心接口,无需了解内部具体实现。例如,数据获取模块内部使用何种数据库或网络请求库,对于调用该模块的其他模块来说是无关紧要的。
- 灵活性:封装内部实现使得模块可以在不影响其他模块的情况下,对自身实现进行修改和优化。例如,数据获取模块可以从使用 HTTP 原生请求切换到使用 Retrofit,只要接口不变,其他依赖模块不受影响。
举例说明
假设我们有一个电商应用,有商品展示模块和购物车模块。
- 接口设计:商品展示模块提供一个接口
ProductInfoProvider
,用于获取商品信息。
interface ProductInfoProvider {
fun getProductDetails(productId: String): ProductDetails
}
购物车模块依赖这个接口来获取商品信息以显示在购物车中。
2. 内部实现封装:商品展示模块内部使用数据库和网络请求来获取商品详细信息,但这些实现细节对购物车模块是隐藏的。购物车模块只通过 ProductInfoProvider
接口获取商品信息,实现了模块间的良好隔离与代码复用。如果商品展示模块需要更换数据获取方式,只要 ProductInfoProvider
接口不变,购物车模块无需修改。