月黑风高,好兄弟发给我一个重构需求,咨询我的意见。
一、 场景分析
开发的产品是需要运行到不同的定制Android板子,不同板子有对应的不同SDK提供的API,目前的业务端,业务流程基本是确定的,比如有业务流程为打开板子的某项开关(需求就是打开开关),对应在板子中可能存在A、B、C三个板子或者更多,其中板子都提供了打开开关S的方法,但是方法名称各不相同,目前在代码中的使用方式都是,创建一个服务于业务的工具类,在工具类中判断板子类型创建不同的SDK,并使用不同SDK的API完成这个需求。
对于商业SDK的开发及多SDK使用,我有丰富的设计经验,面对这个问题,立马能说出这个描述中存在的问题有多少,所以意见是重构!必须重构。
1.1 存在问题
我从来都是以理服人,必须要着说明为什么重构,怎么重构,结果是什么.
1.2 还原代码,暴露问题
分析上述问题,其中的重点有以下几处:
- 多板子(开发平台多,第一反应就是要适配(描述混乱的原因之一就是适配导致的))
- 业务概念统一(什么是业务概念统一呢?举个例子,对于产品而言,在产品需求发布的时候说,当用户点击按钮1时,红灯亮,这就是一个统一的业务概念,因为我们是多板子的开发,我们立马,应该考虑的就是分散性的思考)
- 不同板子都提供了SDK,但是API并不相同(此处可以这样分析一下,对于定制开发的场景中,特别是这种场景下,需求的实现与否只与板子的供应需求是否相吻合(供应需求就是板子自身对外的开放功能))
通过这三点可以看出,这个需求其实很简单,很清晰,但是对于上述的描述的实现方式,肯定是不行的。
为了好解决问题,我们要引入几个实体板子名称(在开发中,领域模型非常重要,事关需求的成功与否、事关团队的配合度高低), 假设目前面对的板子有例如树莓派(Raspberry Pi)、小米开发板、华为开发板。然后还原一下代码
假设工具类名称为ControlBroadUtil, 还原代码如下
这大概就是描述还原的代码,这问题就很清晰了。
- 硬编码板子类型判断: 目前的实现方式中,通过在业务工具类中硬编码板子类型判断,这会导致代码的脆弱性。一旦有新的板子类型加入,就需要修改代码,可能引入新的错误。
- SDK方法名称不一致: 不同板子的SDK提供了相同功能的方法,但方法名称却不同。这种情况可能会导致混乱,使得代码变得难以理解和维护。
- 紧耦合的业务工具类: 目前的设计中,业务工具类负责判断板子类型并选择相应的SDK。这样的紧耦合设计违反了单一职责原则,使得代码难以测试和扩展。
- 可维护性差: 随着板子类型的增多,业务工具类会变得越来越庞大,难以维护。任何一次修改都可能引发意外的问题。
- 扩展性问题: 当需要支持新的板子类型时,目前的设计需要修改现有代码,而不是简单地添加新的板子类型的实现。这降低了系统的可扩展性。
- 缺乏抽象层: 目前的实现没有明确的抽象层,使得在引入新板子类型时无法简单地使用接口进行统一操作。这违反了面向对象设计的一些原则。
- 缺乏文档和规范: 由于不同板子类型的方法名称不同,缺乏清晰的文档和规范,团队成员可能难以理解和使用不同板子的SDK。
二、 谈谈重构思路,有哪些预留项
面对这类型的问题,其实老手第一眼就想到重构的方式了,我的建议是在给出重构意见时必须考虑后续的问题。
2.1 使用抽象工厂模式(目的就是简化、统一、分离创建流程,使用抽象工厂模式可以满足)
- SDK的创建的独立互不影响,对应SDK的配置项都可以在自己的工厂中完成(为什么要用抽象工厂不用简单工厂?因为后续对于一个板子的变化维度可能超过两个(多套API))
- 可以根据不同的板子进行合理的选择
- 在特定场景中可以自由切换,比如华为的不同版本板子,在升级之后的切换场景。
2.2 类图结构如下
2.2.1 SDK创建UML
其中:
ControlBoardFactory (抽象工厂):它声明了一组用于创建一族产品的方法,每个方法对应一种产品,在这里就是每一种板子的SDK。
/**
*
* @author: kpa
* @date: 2024/2/7
* @description: 它声明了一组用于创建一族产品的方法,每个方法对应一种产品,在这里就是每一种板子的SDK。
*/
abstract class ControlBoardFactory<out T : ControlBoardService> {
abstract fun createControlBoard(): T
}
HuaweiFactory等(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每种产品都位于某个产品等级结构中。
/**
*
* @author: kpa
* @date: 2024/2/7
* @description:huawei 板子的创建工厂
*/
class HuaweiControlBoardFactory : ControlBoardFactory<HuaweiBoardServiceImpl>() {
override fun createControlBoard(): HuaweiBoardServiceImpl {
return HuaweiBoardServiceImpl()
}
}
ControlBoardService(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
/**
*
* @author: kpa
* @date: 2024/2/7
* @description: 抽象产品,对应业务,为需求接口
*/
interface ControlBoardService {
fun switch(switchValue: Int)
}
HuaweiControlBordImpl等(具体产品):它定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法。
/**
*
* @author: kpa
* @date: 2024/2/7
* @description:
*/
class HuaweiBoardServiceImpl : ControlBoardService {
override fun switch(switchValue: Int) {
TODO("Not yet implemented")
}
}
2.2.2 SDK API使用UML
对于类中提的提供方接口将使用适配器模式完成与已知SDK API适配,此处设计的目的是:
- 需求开发中,不需要考虑具体的实现,我们应该做到抽象的需求接口和产品需求是一致的。这样我们即使撤走部分SDK或者添加n中SDK都不会影响我们的业务。
- SDK的API我们是不可以介入编程的,所以他在编码体系中只能是直接使用,但是直接使用就会导致代码的耦合性太高,对三方的依赖太强可不是什么好事
所以决定对抽象产品(ControlBoardService)部分使用适配器模式进行设计,要求是:
- 业务方无感SDK的调用但要调用
- 业务方可以多组合+自主实现
组合方式:
class HuaweiBoardServiceImpl : ControlBoardService {
private val huaweiBoardSDK: HuaweiBoardSDK by lazy { HuaweiBoardSDK() }
override fun switch(switchValue: Int) {
huaweiBoardSDK.huaweiOpenSwitch(switchValue)
}
}
2.2.3 外观模式,保持业务掉用的整洁
如果使用上述的代码,在一套体系中还是会出现调用混乱的问题,呐,处理方式就是使用外观模式,右边部分为外观模式下的物理、逻辑结构。
外观模式相对简单。 类图就不画了代码如下:
/**
*
* @author: kpa
* @date: 2024/2/7
* @description: 外观模式下的工具类
*/
object ControlBroadUtil {
private val huaweiBoardFactory: HuaweiControlBoardFactory by lazy { HuaweiControlBoardFactory() }
// 其他工厂
//...
// 需求接口,面相该接口编程
private var controlBoardService: ControlBoardService? = null
/**
* 供应商环境
*/
private var supplierEnvironment = ""
private fun init() {
// 统一配置读取
supplierEnvironment = System.getProperty("")
controlBoardService = huaweiBoardFactory.createControlBoard()
}
/**
* 直接使用接口编程
*/
public fun getControlBoardService(): ControlBoardService {
return controlBoardService ?: HuaweiControlBoardFactory().createControlBoard()
}
}
三、重构结果分析
经过以上的分析和重构思路,可以得出以下重构结果分析:
3.1 抽象工厂模式的优点
- 松耦合: 抽象工厂模式将产品的创建与使用分离,使得系统更加灵活,减少了模块间的直接依赖,达到松耦合的效果。
- 可扩展性: 当需要增加新的板子类型时,只需添加新的具体工厂和产品类,无需修改已有代码,符合开闭原则,提高了系统的可扩展性。
- 统一接口: 抽象工厂模式提供了一组统一的接口,使得客户端无需关心不同板子的具体实现细节,从而简化了客户端代码。
- 业务概念统一: 通过抽象工厂模式,可以将不同板子的SDK统一到一组接口中,使得业务概念更加清晰和统一。
3.2 适配器模式的优点
- 解耦: 适配器模式将业务代码与SDK的具体实现解耦,业务方无需关心底层SDK的细节,提高了代码的可维护性和可读性。
- 灵活性: 适配器模式使得业务方可以更灵活地选择和切换不同的SDK,而无需修改业务代码,降低了对SDK的依赖性。
- 可扩展性: 当需要添加新的SDK时,只需实现适配器接口即可,无需修改已有代码,符合开闭原则,提高了系统的可扩展性。
四、总结
通过对现有代码的分析和重构,我们解决了原有代码存在的问题,提高了系统的可维护性、可扩展性和可读性。使用抽象工厂模式和适配器模式,使得系统更加灵活,业务概念更加统一,业务代码与底层SDK的实现解耦。这样的设计不仅适应了当前的业务需求,还为未来的扩展和变化提供了良好的支持。
在实际开发中,重构是一个不断演进的过程,需要根据实际情况灵活运用设计模式和原则,不断优化和改进代码结构。同时,良好的文档和规范也是团队协作的重要保障,能够使团队成员更加容易理解和使用不同板子的SDK。