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


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.hqwc.cn/news/470443.html

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

相关文章

统一数据返回格式 及 可能遇到的问题;统一异常处理

统一数据返回格式 统一数据返回格式就像我们寄快递一样&#xff0c;不管你需要寄的东西具体是什么都需要将它打包到统一的快递箱中。 此时我们需要一个“快递箱”用来将返回的数据“装”在里面。这个类是根据业务情况来自行定义的。 Data public class Resp<T> {//200…

arkTS开发鸿蒙OS个人商城案例【2024最新 新年限定开发案例QAQ】

龙年前述 源码获取>文章下方二维码&#xff0c;回复关键字“鸿蒙OS商场源码” 前言 arkTS是华为自己研发的一套前端语言&#xff0c;是在js和ts技术的基础上又进行了升级而成&#xff01; 本篇文章会带领大家通过arkTSnode.jsmongoDB来完成一个鸿蒙OS版本的商城案例&…

vue3 之 商城项目—支付

支付模版 pay/index.vue <script setup> const payInfo {} </script> <template><div class"xtx-pay-page"><div class"container"><!-- 付款信息 --><div class"pay-info"><span class"ic…

《Linux 简易速速上手小册》第4章: 包管理与软件安装(2024 最新版)

文章目录 4.1 包管理基础4.1.1 重点基础知识4.1.2 重点案例&#xff1a;在 Ubuntu 上安装和管理软件4.1.3 拓展案例 1&#xff1a;添加软件仓库4.1.4 拓展案例 2&#xff1a;回滚软件到旧版本 4.2 使用 APT 与 YUM4.2.1 重点基础知识4.2.2 重点案例&#xff1a;在 Ubuntu 上配置…

C# winfrom实例:四路激光测距雷达数据采集和波形图绘制

1.所述产品 产品型号&#xff1a;TFmini Plus 相关资料下载地址&#xff1a;http://www.benewake.com/download 产品名称&#xff1a;TFmini Plus激光雷达模组制造商公司&#xff1a;北醒&#xff08;北京&#xff09;光子科技有限公司 2.产品功能&#xff1a;TFmini Plus是基…

MySQL篇----第十四篇

系列文章目录 文章目录 系列文章目录前言一、MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?二、锁的优化策略三、索引的底层实现原理和优化四、什么情况下设置了索引但无法使用前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽…

Android之Android.bp文件格式语法(一百八十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Docker 在window 2024版笔记 下载 安装 操作 配置

---Docker 前言--- Docker windows版官方版是一款专业开源的应用容器引擎&#xff0c;可以加快用户构建、共享和运行现代应用程序的速度&#xff0c;支持运行Linux和Windows Docker容器。 Docker 在容器的基础上&#xff0c;进行了进一步的封装&#xff0c;从文件系统、网络互…

fluent脱硝SCR相对标准偏差、氨氮比、截面速度计算

# -*- coding: utf-8 -*- """ Created on Wed Sep 20 20:40:30 2023 联系QQ:3123575367&#xff0c;专业SCR脱硝仿真。 该程序用来处理fluent通过export-solution-ASCII-Space导出的数据&#xff0c;可计算标准偏差SD、相对标准偏差RSD,适用于求解平面的相对均匀…

RK3568笔记十六:Framebuffer实验

若该文为原创文章&#xff0c;转载请注明原文出处。 本意是移植LVGL&#xff0c;但在编译DRM过程中一直编译失败&#xff0c;然后就想Framebuffer是否可以用&#xff0c;所以测试一下。 一、framebuffer介绍 FrameBuffer中文译名为帧缓冲驱动&#xff0c;它是出现在2.2.xx内…

23款奔驰S400商务版没有后排电动座椅那改装一套跟选装有区别吗

改装的后排电动座椅通常提供以下功能&#xff1a; 电动调节&#xff1a;座椅可以通过按钮或控制面板进行前后调节&#xff0c;以适应乘客的腿部空间需求。 靠背角度调节&#xff1a;乘客可以通过电动调节功能来调整座椅的靠背角度&#xff0c;以获得更舒适的坐姿。 座椅倾斜调…

Java与JavaScript的区别与联系

Java是目前编程领域使用非常广泛的编程语言&#xff0c;相较于JavaScript&#xff0c;Java更被人们熟知。很多Java程序员想学门脚本语言&#xff0c;一看JavaScript和Java这么像&#xff0c;很有亲切感&#xff0c;那干脆就学它了&#xff0c;这也间接的帮助了JavaScript的发展…