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


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.mzph.cn/news/676090.shtml

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

相关文章

Python中HTTP隧道的基本原理与实现

HTTP隧道是一种允许客户端和服务器之间通过中间代理进行通信的技术。这种隧道技术允许代理服务器转发客户端和服务器之间的所有HTTP请求和响应&#xff0c;而不需要对请求或响应内容进行任何处理或解析。Python提供了强大的网络编程能力&#xff0c;可以使用标准库中的socket和…

单片机学习笔记---DS1302实时时钟工作原理

目录 DS1302介绍 学会读芯片手册&#xff08;DS1302芯片手册&#xff09; 封装 引脚定义 电源部分 时钟部分 通信部分 总结列表 内部结构图 电源控制部分 时钟控制部分 寄存器部分 访问部分 寄存器部分的详细定义 命令字 时序的定义 单字节读 单字节写 提前预…

【JAVA WEB】CSS

目录 CSS是什么&#xff1f; 基本语法规范 引入方式 内部样式表 行内样式表 外部样式表 常用选择器的种类 基础选择器 标签选择器 类选择器 id选择器 通配符选择器 复合选择器 后代选择器 伪类选择器 常用元素属性&#xff1a; 字体属性&#xff1a; 文本属性…

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(一)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 机器学习海啸 2006 年&#xff0c;Geoffrey Hinton 等人发表了一篇论文&#xff0c;展示了如何训练一个能够以最先进的精度…

Mac 下JDK环境变量配置 及 JDK多版本切换

一、推荐官网下载&#xff1a; 二、环境变量配置 1、查看JDK地址&#xff0c;在终端输入以下命令&#xff1a; /usr/libexec/java_home -V 我的路径&#xff1a; /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home /Library/Java/JavaVirtualMachines/zulu-11.j…

图像批量重命名(基于Python,本地运行)

图像批量重命名(基于Python&#xff0c;本地运行) &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;场景假设&#x1f333;&#x1f333;知识储备&#x1f333;os.path.splitext方法语法示例 os.listdir方法语法示例 &#x1f333;解决方案&am…

Springboot 整合 Elasticsearch(二):使用HTTP请求来操作ES

&#x1f4c1;前情提要&#xff1a;Springboot整合Elasticsearch&#xff08;一&#xff09;&#xff1a;Linux下安装 Elasticsearch 8.x 目录 一、使用 elasticsearch-head 插件连接 1、下载压缩包 2、在 chrome 浏览器中添加扩展程序 3、修改IP地址&#xff0c;点击连接 …

深度学习系列57: 清华大模型MiniCPM上手

MiniCPM 是面壁智能与清华大学自然语言处理实验室共同开源的系列端侧大模型&#xff0c;主体语言模型 MiniCPM-2B 仅有 24亿&#xff08;2.4B&#xff09;的非词嵌入参数量 1. 上手对比测试 mps比cpu大概快了9倍左右。 也可以在modelspore上测试&#xff1a;

【机器学习】数据清洗之处理缺失点

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步…

【CC++】内存管理1:new + delete

前言 之前我们学习过C语言中的内存管理&#xff08;各种函数&#xff09;今天我们来学习C中的内存管理 引入 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {…

Bee+SpringBoot稳定的Sharding、Mongodb ORM功能(同步 Maven)

Hibernate/MyBatis plus Sharding JDBC Jpa Spring data GraphQL App ORM (Android, 鸿蒙) Bee 小巧玲珑&#xff01;仅 860K, 还不到 1M, 但却是功能强大&#xff01; V2.2 (2024春节・LTS 版) 1.Javabean 实体支持继承 (配置 bee.osql.openEntityCanExtendtrue) 2. 增强批…

c#: 表达式树的简化

环境&#xff1a; .net 6 一、问题&#xff1f; 有下面的表达式&#xff1a; var nums new List<int> { 1, 2, 3 }; Expression<Func<int, bool>> exp i > i > nums.Max();我们知道&#xff0c;它其实就是&#xff1a;exp i > i > 3; 那么…

redis双写一致

redis双写一致&#xff0c;指的是redis缓存与mysql数据同步 双写一致常见方案有很多&#xff1a; 同步双写&#xff1a;更新完mysql后立即同时更新redis mq同步&#xff1a;程序在更新完mysql后&#xff0c;投递消息到中间键mq&#xff0c;一个程序监听mq&#xff0c;获得消…

商汤科技「日日新4.0」正式发布,多维度升级大模型体系,能力比肩GPT-4!

文 | BFT机器人 近日&#xff0c;商汤科技正式发布「日日新SenseNova 4.0」&#xff0c;宣告大模型体系多维度全面升级。这款模型具备更全面的知识覆盖、更可靠的推理能力&#xff0c;以及更优越的长文本理解和数字推理能力。同时&#xff0c;它还支持跨模态交互&#xff0c;为…

【跳槽须知】关于企业所签订的竞业协议你知道多少?

年后跳槽须知自己签订的合同中是否存在竞业协议&#xff0c;谨防协议造成经济损失 &#x1f413; 什么是竞业协议 竞业协议时用于保护自己的权益&#xff0c;在员工离职时决定是否启动的一种协议&#xff0c;避免一些掌握公司机密的一些重要岗位人才流入竞争对手的公司&#xf…

解决MapboxGL的Popup不支持HTMLDiv元素的问题

解决MapboxGL的Popup不支持HTMLDiv元素的问题 官网给出的文档是不支持HTMLDivElement的&#xff0c;只支持HTML标签。 如果单纯的只显示字符串&#xff0c;那就没问题&#xff0c;如果想在Popup中使用更强大的功能&#xff0c;此时就不行了&#xff0c;下面是源码的一部分显示…

WordPress如何实现随机显示一句话经典语录?怎么添加到评论框中?

我们在一些WordPress网站的顶部或侧边栏或评论框中&#xff0c;经常看到会随机显示一句经典语录&#xff0c;他们是怎么实现的呢&#xff1f; 其实&#xff0c;boke112百科前面跟大家分享的『WordPress集成一言&#xff08;Hitokoto&#xff09;API经典语句功能』一文中就提供…

nginx+flask+Gunicorn反代理服务拿不到真实IP的解决

背景 本人在宝塔linux环境&#xff0c;要部署flask的简单后端并且用Ngnix反代理&#xff0c;用Gunicorn框架部署。&#xff08;o(╥﹏╥)o中间磕磕绊绊总算部署上去了&#xff0c;需要了解Gunicorn怎么部署的朋友&#xff0c;评论区留言&#xff0c;我加补一篇介绍&#xff09;…

32I2C通信协议

异步时序的&#xff1a;非常依赖硬件外设的支持&#xff0c;比如串口是很难用软件来模拟的&#xff1b;但节省了一根时钟线的资源 同步时序可以极大地降低单片机对硬件电路的依赖&#xff0c;时钟线停止了&#xff0c;发送方和接收方都会停止 一.I2C通信协议简介 二.硬件电路…

[WUSTCTF2020]朴实无华(特详解)

一开始说header出问题了 就先dirsaerch扫一遍 发现robot.txt 访问一下 去看看&#xff0c;好好好&#xff0c;肯定不是得 他一开始说header有问题&#xff0c;不妨抓包看看&#xff0c;果然有东西 访问看看&#xff0c;乱码修复一下&#xff0c;在之前的博客到过 <img src…