设计模式巡礼:多板适配案例解析与深度重构

news2024/11/14 23:58:39

theme: cyanosis

月黑风高,好兄弟发给我一个重构需求,咨询我的意见。

一、 场景分析

开发的产品是需要运行到不同的定制Android板子,不同板子有对应的不同SDK提供的API,目前的业务端,业务流程基本是确定的,比如有业务流程为打开板子的某项开关(需求就是打开开关),对应在板子中可能存在A、B、C三个板子或者更多,其中板子都提供了打开开关S的方法,但是方法名称各不相同,目前在代码中的使用方式都是,创建一个服务于业务的工具类,在工具类中判断板子类型创建不同的SDK,并使用不同SDK的API完成这个需求。

对于商业SDK的开发及多SDK使用,我有丰富的设计经验,面对这个问题,立马能说出这个描述中存在的问题有多少,所以意见是重构!必须重构。

1.1 存在问题

我从来都是以理服人,必须要着说明为什么重构,怎么重构,结果是什么.

images.jpeg

1.2 还原代码,暴露问题

分析上述问题,其中的重点有以下几处:

  1. 多板子(开发平台多,第一反应就是要适配(描述混乱的原因之一就是适配导致的))
  2. 业务概念统一(什么是业务概念统一呢?举个例子,对于产品而言,在产品需求发布的时候说,当用户点击按钮1时,红灯亮,这就是一个统一的业务概念,因为我们是多板子的开发,我们立马,应该考虑的就是分散性的思考)
  3. 不同板子都提供了SDK,但是API并不相同(此处可以这样分析一下,对于定制开发的场景中,特别是这种场景下,需求的实现与否只与板子的供应需求是否相吻合(供应需求就是板子自身对外的开放功能))

通过这三点可以看出,这个需求其实很简单,很清晰,但是对于上述的描述的实现方式,肯定是不行的。

为了好解决问题,我们要引入几个实体板子名称(在开发中,领域模型非常重要,事关需求的成功与否、事关团队的配合度高低), 假设目前面对的板子有例如树莓派(Raspberry Pi)、小米开发板、华为开发板。然后还原一下代码

假设工具类名称为ControlBroadUtil, 还原代码如下

image.png

这大概就是描述还原的代码,这问题就很清晰了。

  1. 硬编码板子类型判断: 目前的实现方式中,通过在业务工具类中硬编码板子类型判断,这会导致代码的脆弱性。一旦有新的板子类型加入,就需要修改代码,可能引入新的错误。
  2. SDK方法名称不一致: 不同板子的SDK提供了相同功能的方法,但方法名称却不同。这种情况可能会导致混乱,使得代码变得难以理解和维护。
  3. 紧耦合的业务工具类: 目前的设计中,业务工具类负责判断板子类型并选择相应的SDK。这样的紧耦合设计违反了单一职责原则,使得代码难以测试和扩展。
  4. 可维护性差: 随着板子类型的增多,业务工具类会变得越来越庞大,难以维护。任何一次修改都可能引发意外的问题。
  5. 扩展性问题: 当需要支持新的板子类型时,目前的设计需要修改现有代码,而不是简单地添加新的板子类型的实现。这降低了系统的可扩展性。
  6. 缺乏抽象层: 目前的实现没有明确的抽象层,使得在引入新板子类型时无法简单地使用接口进行统一操作。这违反了面向对象设计的一些原则。
  7. 缺乏文档和规范: 由于不同板子类型的方法名称不同,缺乏清晰的文档和规范,团队成员可能难以理解和使用不同板子的SDK。

二、 谈谈重构思路,有哪些预留项

面对这类型的问题,其实老手第一眼就想到重构的方式了,我的建议是在给出重构意见时必须考虑后续的问题。

2.1 使用抽象工厂模式(目的就是简化、统一、分离创建流程,使用抽象工厂模式可以满足)

  • SDK的创建的独立互不影响,对应SDK的配置项都可以在自己的工厂中完成(为什么要用抽象工厂不用简单工厂?因为后续对于一个板子的变化维度可能超过两个(多套API))
  • 可以根据不同的板子进行合理的选择
  • 在特定场景中可以自由切换,比如华为的不同版本板子,在升级之后的切换场景。

2.2 类图结构如下

2.2.1 SDK创建UML

image.png

其中:

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适配,此处设计的目的是:

  1. 需求开发中,不需要考虑具体的实现,我们应该做到抽象的需求接口和产品需求是一致的。这样我们即使撤走部分SDK或者添加n中SDK都不会影响我们的业务。
  2. SDK的API我们是不可以介入编程的,所以他在编码体系中只能是直接使用,但是直接使用就会导致代码的耦合性太高,对三方的依赖太强可不是什么好事

所以决定对抽象产品(ControlBoardService)部分使用适配器模式进行设计,要求是:

  1. 业务方无感SDK的调用但要调用
  2. 业务方可以多组合+自主实现

image.png

组合方式:

class HuaweiBoardServiceImpl : ControlBoardService {  
    private val huaweiBoardSDK: HuaweiBoardSDK by lazy { HuaweiBoardSDK() }  
  
    override fun switch(switchValue: Int) {  
        huaweiBoardSDK.huaweiOpenSwitch(switchValue)  
    }  
}
2.2.3 外观模式,保持业务掉用的整洁

image.png

如果使用上述的代码,在一套体系中还是会出现调用混乱的问题,呐,处理方式就是使用外观模式,右边部分为外观模式下的物理、逻辑结构。

外观模式相对简单。
类图就不画了代码如下:

/**  
*  
* @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 抽象工厂模式的优点
  1. 松耦合: 抽象工厂模式将产品的创建与使用分离,使得系统更加灵活,减少了模块间的直接依赖,达到松耦合的效果。
  2. 可扩展性: 当需要增加新的板子类型时,只需添加新的具体工厂和产品类,无需修改已有代码,符合开闭原则,提高了系统的可扩展性。
  3. 统一接口: 抽象工厂模式提供了一组统一的接口,使得客户端无需关心不同板子的具体实现细节,从而简化了客户端代码。
  4. 业务概念统一: 通过抽象工厂模式,可以将不同板子的SDK统一到一组接口中,使得业务概念更加清晰和统一。
3.2 适配器模式的优点
  1. 解耦: 适配器模式将业务代码与SDK的具体实现解耦,业务方无需关心底层SDK的细节,提高了代码的可维护性和可读性。
  2. 灵活性: 适配器模式使得业务方可以更灵活地选择和切换不同的SDK,而无需修改业务代码,降低了对SDK的依赖性。
  3. 可扩展性: 当需要添加新的SDK时,只需实现适配器接口即可,无需修改已有代码,符合开闭原则,提高了系统的可扩展性。

四、总结

通过对现有代码的分析和重构,我们解决了原有代码存在的问题,提高了系统的可维护性、可扩展性和可读性。使用抽象工厂模式和适配器模式,使得系统更加灵活,业务概念更加统一,业务代码与底层SDK的实现解耦。这样的设计不仅适应了当前的业务需求,还为未来的扩展和变化提供了良好的支持。

在实际开发中,重构是一个不断演进的过程,需要根据实际情况灵活运用设计模式和原则,不断优化和改进代码结构。同时,良好的文档和规范也是团队协作的重要保障,能够使团队成员更加容易理解和使用不同板子的SDK。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1438929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLM大语言模型(六):RAG模式下基于PostgreSQL pgvector插件实现vector向量相似性检索

目录 HightLightMac上安装PostgreSQLDBever图形界面管理端创建DB 使用向量检索vector相似度计算近似近邻索引HNSW近似近邻索引示例 HightLight 使用PostgreSQL来存储和检索vector&#xff0c;在数据规模非庞大的情况下&#xff0c;简单高效。 可以和在线业务共用一套DB&#…

国产航顺HK32F030M: 超声波测距模块串口通信数据接收与处理

参考代码 /************************************************************************************************** * file usart_async_tx_no_int_rx_rxneint.c * brief 异步串口通信例程, 通过查询TXE标志发送数据,通过RXNE中断接收数据,当中断接收到数据后会将 * …

使用yolo训练自己的模型

YOLO&#xff08;You Only Look Once&#xff09;是一种用于目标检测的深度学习模型&#xff0c;旨在实时检测图像或视频中的多个对象。与传统的目标检测方法不同&#xff0c;YOLO一次性处理整个图像&#xff0c;而不是通过滑动窗口或区域提议进行多次检测。这种方法使得YOLO在…

2023年全国职业院校技能大赛软件测试赛题第3套

2023年全国职业院校技能大赛 软件测试赛题第3套 赛项名称&#xff1a; 软件测试 英文名称&#xff1a; Software Testing 赛项编号&#xff1a; GZ034 归属产业&#xff1a; 电子与信息大类 …

微信小程序新手入门教程四:样式设计

WXSS (WeiXin Style Sheets)是一套样式语言&#xff0c;用于描述 WXML 的组件样式&#xff0c;决定了 WXML 的组件会怎么显示。 WXSS 具有 CSS 大部分特性&#xff0c;同时为了更适合开发微信小程序&#xff0c;WXSS 对 CSS 进行了扩充以及修改。与 CSS 相比&#xff0c;WXSS …

GPT帮别人画,就是不帮我画,我很急怎么办?

今天分享如何让GPT更听话&#xff0c;分享来自林杰陪伴群中的群友“友人”&#xff0c;分享的非常棒&#xff0c;是AI绘画中的高手&#xff01; AI也需要奖励 虽然说AI距离完全的自主思考还有很长很长的路要走&#xff0c;但是各种实验表明&#xff0c;当我们在与AI对话时&am…

ABAP 标准状态栏GUI STATUS的快速创建

ABAP 标准状态栏GUI STATUS的快速创建 不用先创建GUI 状态 SE41

【PyQt】06-.ui文件转.py文件

文章目录 前言方法一、基本脚本查看自己的uic安装目录 方法二、添加到扩展工具里面&#xff08;失败了&#xff09;方法二的成功步骤总结 前言 方法一、基本脚本 将Qt Designer&#xff08;一种图形用户界面设计工具&#xff09;生成的.ui文件转换为Python代码的脚本。 pytho…

国考省考行测:平行结构体

国考省考行测&#xff1a;平行结构体 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申论和行测的重要知识点 遇…

Linux命令:du命令和sort命令

目录 1 du命令1.1 du命令说明## 1.2 实例-a&#xff1a;显示当前目录下所有文件和目录-s&#xff1a;显示当前目录下所有文件和目录总大小--max-depth&#xff1a;显示当前目录&#xff0c;目录深度为1的&#xff0c;所有目录的总大小-k&#xff1a;输出内容以 kb 单位显示磁盘…

算法:构成的正方形数量

一、算法描述 输入 N 个互不相同的二维整数坐标, 求这 N 个坐标可以构成的正方形数量。(内积为零的两个向量垂直) 第一行输入为 N&#xff0c;N 代表坐标数量&#xff0c;N为正整数。N < 100 之后的 K 行输入为坐标 x y以空格分隔&#xff0c;x, y 为整数, -10 < x,y <…

Jedis和SpringDataRedis快速入门

Jedis快速入门 Jedis连接池 SpringDataRedis快速入门 序列化 引入SpringMVC就不用再引入这个依赖

华为配置交换机KPI信息上报分析器示例组网图形

配置交换机KPI信息上报分析器示例 组网图形 图1 KPI信息上报拓扑图 组网需求操作步骤配置文件 组网需求 如图1所示&#xff0c;某企业网络用一台华为公司iMaster NCE-CampusInsight作为分析器对交换机设备进行智能运维管理。iMaster NCE-CampusInsight与交换机之间已经实现路由…

(2024,VLM,操纵链)CogCoM:训练大型视觉语言模型,通过操作链深入细节

CogCoM: Train Large Vision-Language Models Diving into Details through Chain of Manipulations 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 方法 2.1. 术语 2.2…

14 归并排序和其他排序

1.归并排序 2.计数排序 1. 归并排序 基本思想 建立在归并操作上的一种排序算法,采用分治法的一个典型应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff0c;将两个有序表合成一个称为二路归并。 原数组无序&#xff0c;以中间分割为两个数组&#xff0c;…

Mysql进阶(sql优化和explain关键字)

一、为什么要对SQL进行优化&#xff1f; 由于业务数据量的增多&#xff0c;SQL的执行效率对程序的运行效率影响增大&#xff0c;此时就需要对SQL进行优化。 二、SQL优化的方法 1.查询sql尽量不要使用select * &#xff0c;而是具体字段。 节省资源&#xff0c;减少开销。 …

国际物流数字化运输方式选择指南 | 箱讯科技

国际物流涉及多种运输方式&#xff0c;每种方式都有其独特的优势和适用场景。选择合适的运输方式对于确保货物安全、及时到达目的地并控制成本至关重要。以下是对六种主要国际运输方式的简要介绍和选择建议&#xff1a; 国际快递&#xff1a;适用于小件、高价值或急需的货物。…

Response对象实现设置响应数据

1.设置响应数据功能 2.response设置重定向 重定向是一种资源跳转方式&#xff0c;类似于转发&#xff0c;但存在不同&#xff0c;转发是资源a处理了一部分&#xff0c;再跳转到资源b继续处理&#xff0c;最后响应。 实现方式一&#xff1a; resp.setStatus(302); resp.setHe…

力扣:42. 接雨水 84.柱状图中最大的矩形(单调栈,双指针)

这两道题解题思路类似&#xff0c;一个是单调递增栈&#xff0c;一个是单调递减栈。本篇博客给出暴力&#xff0c;双指针和单调栈解法。 42. 接雨水 题目&#xff1a; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后…

MQTT在linux下服务端和客户端的应用

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、开放标准的消息传输协议&#xff0c;设计用于受限设备和低带宽、不稳定网络的通信。 MQTT的一些关键特点和概念&#xff1a; 发布/订阅模型&#xff1a; MQTT采用发布/订阅&#xff08;Publ…