当前位置: 首页 > news >正文

Android 常用设计模式和实例

一、什么是设计模式?

设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

好了,上面其实都是废话,摘抄的别人的。

开发好几年了,没有特别去注意过自己的项目设计要怎样怎样,其实在无形中,实现了各种各样的设计模式。在这儿做个总结。

二、设计模式的六个原则

设计模式的 六大原则 是软件设计的核心思想,它们确保代码的 高内聚、低耦合,提高代码的 可读性、扩展性和维护性

1、迪米特法则,又称最少知道原则(Demeter Principle)

2、里氏替换原则,即基类和子类之间的关系。子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

3、依赖倒转原则,即降低客户与实现模块之间的耦合。就是该用抽象类和接口的时候尽量用,不要去依赖具体的实现类。

  • 高层模块不应该依赖低层模块,而应该依赖抽象
  • 细节(具体实现)应该依赖于抽象(接口)

4、接口隔离原则,不要强迫一个类实现它用不到的接口,应拆分接口,让接口更小更具体

比如一个人有生活,工作和睡觉,每个事物都有很多方法去实现,不应该把它全放在人这个接口,而是拆分出去。避免无用方法。

5.开闭原则,对扩展开放,对修改关闭,即在不修改已有代码的情况下,允许扩展功能。

  • 避免修改已有代码,减少风险
  • 支持扩展,提高系统的灵活性

6.单一职责原则,一个类应该 只有一个引起它变化的原因,即一个类只做一件事。别一个类把所有事儿包圆了,代码臃肿且不好维护迭代。

三、开发者常用到的设计模式及案例

1.单例模式

📌 作用:确保某个类在整个应用生命周期中只有一个实例,并提供一个全局访问点。这个在开发中会经常使用到。

📌 场景

  • 全局管理对象(如 SharedPreferences、数据库 Room 实例)
  • 网络请求管理(Retrofit)
  • 应用程序级别的配置管理

示例1:retrofit(kotlin)

object RetrofitClient {private val retrofit: Retrofit by lazy {Retrofit.Builder().baseUrl("https://api.example.com/").addConverterFactory(GsonConverterFactory.create()).build()}fun getService(): ApiService = retrofit.create(ApiService::class.java)
}

优点:使用 lazy 懒加载,只有在真正需要时才会创建 Retrofit 实例,避免应用启动时的额外资源占用。

当然,我现在项目使用的是枚举enum

enum class RetrofitManager {INSTANCE;val retrofit: Retrofitinit {retrofit = getRetrofitObject()}private fun getRetrofitObject(): Retrofit {val client = OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()return Retrofit.Builder().baseUrl(AppAPI.BASE_URL).client(client).addConverterFactory(GsonConverterFactory.create()).build()}
}

在 Kotlin 中,使用 enum class 创建单例是有效的,因为:

  1. 枚举的实例是唯一的
    • INSTANCE 只会在 RetrofitManager 类加载时创建一次,符合单例模式的定义
  2. JVM 保证枚举单例的线程安全
    • enum 的实例在类加载时就初始化,JVM 确保只有一个实例,避免了反射攻击和反序列化创建新实例的问题
2.工厂模式

📌 作用:通过工厂方法创建对象,而不是直接 new,提高代码的可扩展性和维护性。
📌 场景

  • 不同类型的 ViewHolder 创建
  • 不同类型的对话框(Dialog)创建
  • 网络请求数据解析(如 Gson TypeAdapter)

例如我们在实现一个多类型的列表时,需要在 onCreateViewHolder() 里写多个 when 语句进行条件判断去inflate不同的布局。

例如:

override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): RecyclerView.ViewHolder {return if (viewType == TYPE_ITEM) {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_home_more_type, parent, false)MoreVm(view, itemClickListener)} else {val view = LayoutInflater.from(parent.context).inflate(R.layout.item_footer_loading, parent, false)FooterViewHolder(view)}}

这时候,我们就可以用到工厂模式,可以提高代码复用性。

class ViewHolderFactory {companion object {fun create(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {return when (viewType) {TYPE_TEXT -> TextViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false))TYPE_IMAGE -> ImageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false))else -> throw IllegalArgumentException("Unknown viewType: $viewType")}}}
}
3.观察者模式

作用:允许对象间定义一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。
📌 场景

  • LiveData + ViewModel(MVVM 架构)
  • EventBus / RxJava
  • 广播接收器(BroadcastReceiver)

这个在现在的开发里用的地方太多了,示例:

class MyViewModel : ViewModel() {val userData: MutableLiveData<String> = MutableLiveData()fun updateUserData(newData: String) {userData.value = newData}fun requestData(){...updateUserData(newData)...}
}// Activity 中监听数据变化
viewModel.userData.observe(this, Observer { data ->textView.text = data
})
4.策略模式

作用:定义一组算法,把它们封装起来,并使它们可以互相替换。
📌 场景

  • 不同的图片加载策略(Glide/Picasso)
  • 不同的动画策略
  • 不同的网络请求缓存策略

示例:

interface ImageLoaderStrategy {fun loadImage(context: Context, url: String, imageView: ImageView)
}class GlideImageLoader : ImageLoaderStrategy {override fun loadImage(context: Context, url: String, imageView: ImageView) {Glide.with(context).load(url).into(imageView)}
}class PicassoImageLoader : ImageLoaderStrategy {override fun loadImage(context: Context, url: String, imageView: ImageView) {Picasso.get().load(url).into(imageView)}
}// 使用
val imageLoader: ImageLoaderStrategy = GlideImageLoader()
imageLoader.loadImage(context, "https://example.com/image.jpg", imageView)

🔹 优势:让不同的策略可以在运行时切换,提升代码的灵活性。在平时的开发里,我会用于判断图片大小,是否对图片压缩,图片的缓存方式进行策略模式的设计

5. 责任链模式

 作用:将多个处理逻辑串联起来,按照顺序依次处理请求,直到满足条件的处理器处理完成。记住:责任链模式中的每个处理节点(拦截器、处理者)可以选择是否处理请求,或将其传递给下一个处理者。所谓的链式调用并不符合这个设计模式。
📌 场景

  • OkHttp 的拦截器(Interceptor)
  • Android 事件分发(onTouchEvent)
  • 权限请求链

示例:

    private fun getRetrofitObject(): Retrofit {val loggingInterceptor = HttpLoggingInterceptor { message ->LogUtils.i("RetrofitLog", "retrofitBack = $message")}.apply { level = HttpLoggingInterceptor.Level.BODY }val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).addInterceptor(HeaderInterceptor()).connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()return Retrofit.Builder().baseUrl(AppAPI.BASE_URL).client(client).addConverterFactory(GsonConverterFactory.create()).build()}
}

🔹 优势:可以动态地添加或移除责任节点,提高代码的扩展性。我上面就设置了日志拦截器和请求拦截器,

  • 灵活性:你可以自由添加或删除拦截器,改变请求和响应的处理顺序。
  • 可扩展性:每个拦截器都可以独立工作,允许按需添加处理逻辑,形成一个灵活的扩展点。
  • 解耦:每个拦截器只关注自己需要处理的任务,不必知道其他拦截器的逻辑,符合单一职责原则。
6. 适配器模式

作用:将一个接口转换成客户端期望的接口,使不兼容的类可以协同工作。
📌 场景

  • RecyclerView 适配器(Adapter)
  • 数据库适配(Room)

例如后端返回的数据更改了,变成了:

{"userId": "12345","user_name": "Lee","user_email": "lee@example.com"
}

而我之前设计的实体类是:

data class UserProfile(val id: String,val name: String,val email: String
)

这时候我只需要写一个方法:

// 适配器类,将 API 数据转换为 UserProfile 结构
class UserAdapter(private val apiData: ApiUser) {fun toUserProfile(): UserProfile {return UserProfile(id = apiData.userId,name = apiData.user_name,email = apiData.user_email)}
}

 有一说一,我这个示例,很简陋,能力有限,在需求开发中在这种场景使用过。

🔹 优势

  1. 桥接不同的数据结构,避免 API 数据格式和 UI 组件直接绑定,提升代码灵活性。
  2. 让已有代码无需修改,只需更改适配器,保持项目稳定。
  3. 提高代码复用性,适配器可以被多个 UI 组件或业务模块复用。
7. 代理模式(Proxy Pattern)

作用:为对象提供一个代理类,控制对原对象的访问。
📌 场景

  • 动态权限申请(ActivityCompat.requestPermissions)
  • Retrofit 动态代理
  • AOP(Aspect-Oriented Programming)例如retrofit的日志拦截,埋点等,AOP(面向切面编程)主要是基于代理模式实现的,但它不仅仅局限于代理模式,还可以结合字节码操作(如 ASM、Javassist)和编译器插桩(AspectJ)等方式

AOP 本质上就是通过代理模式,在不修改原代码的情况下,动态地增强功能(如日志、权限控制、监控等)。
在 AOP 的实现中,通常使用 动态代理(JDK 动态代理、CGLIB 代理) 来拦截方法调用,并在方法执行前后执行额外的逻辑。

📌 代理模式核心思路:

  1. 静态代理:预先定义代理类,手动编写代理逻辑(适用于少量接口)。
  2. 动态代理:运行时生成代理对象,动态添加方法增强(适用于大规模 AOP)。
  3. 字节码操作:直接修改 .class 字节码(如 ASM、Javassist、AspectJ)。

说来惭愧,这个设计模式我在自己的项目开发里并没有如何去设计过,不过去了解了一下,

可以使用 Kotlin 注解 + 反射 实现AOP,进行方法执行时间拦截。

示例1:

//定义一个注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class TimeLog//在我要使用的方法上加一个注解
class TestClass {@TimeLogfun testMethod() {Thread.sleep(500)  // 模拟耗时操作}
}
//📌 使用反射 + AOP 拦截
fun logExecutionTime(obj: Any) {val methods = obj::class.java.declaredMethodsfor (method in methods) {if (method.isAnnotationPresent(TimeLog::class.java)) {val start = System.currentTimeMillis()method.invoke(obj)  // 调用方法val end = System.currentTimeMillis()Log.d("AOP", "${method.name} 执行时间: ${end - start}ms")}}
}val test = TestClass()
logExecutionTime(test)

示例2:使用 AspectJ(编译期 AOP)

目的:无须修改任何activity代码,监听 Activity 生命周期,自动埋点

@Aspect
class LifecycleAspect {@Before("execution(* android.app.Activity.onCreate(..))")fun beforeOnCreate(joinPoint: JoinPoint) {val activity = joinPoint.target as ActivityLog.d("AOP", "Activity ${activity::class.java.simpleName} - onCreate")}
}

常用的三方库里面很多都是基于上述的设计模式去实现的,我自己一般可能就只会用到前面四个,哈哈哈

相关文章:

Android 常用设计模式和实例

一、什么是设计模式&#xff1f; 设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问&#xff0c;设计模式于己于他人于系统都是多赢的&#xff0c;设计模式使代码编制真正工程化&#xff0c;设计模式是软件工程的基石&#xff0c;如同大厦的一块块…...

模拟(典型算法思想)—— OJ例题算法解析思路

目录 一、1576. 替换所有的问号 - 力扣(LeetCode) 运行代码: 1. 输入和输出 2. 变量初始化 3. 遍历字符串 4. 替换逻辑 5. 返回结果 整体分析 1. 思路总结 2. 为什么要这样设计 3. 时间复杂度与空间复杂度 4. 边界情况 二、495. 提莫攻击 - 力扣(LeetCode) …...

Nginx配置 ngx_http_proxy_connect_module 模块及安装

1、配置完互联网yum源后,安装相关依赖软件包 [root@server soft]# yum install -y patch pcre pcre-devel make gcc gcc-c++ openssl openssh [root@server soft]# yum install openssl* 2、解压缩软件,加载模块 [root@server soft]# ls nginx-1.20.2 nginx-1.20.2.tar.gz …...

项目质量管理体系及保证措施

项目质量管理体系的核心是建立标准化流程、强化全员参与意识、实施动态监控机制。其中&#xff0c;标准化流程是质量管理的基石。例如&#xff0c;某全球500强企业通过引入ISO 9001体系&#xff0c;将项目缺陷率降低了37%。标准化流程不仅能明确各环节的质量要求&#xff0c;还…...

php 实现 deepSeek聊天对话

deepSeek 在 2025年可以说是火热。它可以说是国内版真正义意上的chatgpt。那么&#xff0c;如果我要实现用php 接入 deepSeek 的api呢。其实&#xff0c;方法也很简单。下面的代码我是自己封装过的&#xff0c;大家可以直接拿来使用&#xff0c;记得自己修改下密钥。 function…...

【Unity】性能优化:UI的合批 图集和优化

目录 前言一、合批测试二、图集 前言 注意&#xff1a;DC指的是Draw Call。 温馨小提示&#xff1a;Frame Debugger 窗口&#xff08;菜单&#xff1a;Window > Analysis > Frame Debugger&#xff09;会显示绘制调用信息&#xff0c;并允许您控制正在构建的帧的“回放”…...

ASP.NET Core SignalR案例:导入英汉词典

Ecdict 下载词典文件stardict.7z&#xff0c;解压&#xff0c;stardict.csv是一个CSV格式的文本文件&#xff0c;文件的第一行是表头&#xff0c;除第一行外&#xff0c;其他每行文本是一个单词的相关信息&#xff0c;用逗号分隔的就是各个列的值。英汉词典ECDICT中导入单词到…...

C++ 通过XML读取参数

目录 方法1&#xff1a;一次读取一个参数&#xff0c;每读取一个参数调用一次函数 方法2&#xff1a;一次性读取一个节点中的所有参数&#xff0c;然后调用一次函数 方法3&#xff1a;一次性读取所有参数 推荐方案 示例代码 总结 0、XML示例 <ConfigurationSettings&…...

WiFi配网流程—SmartConfig 配网流程

目录 &#x1f4cc; SmartConfig 配网流程 &#x1f449; 阶段 1&#xff1a;设备进入配网模式 &#x1f449; 阶段 2&#xff1a;手机 App 发送 Wi-Fi 配置信息 &#x1f449; 阶段 3&#xff1a;设备解析 Wi-Fi 配置&#xff0c;连接家庭网络 &#x1f449; 阶段 4&…...

哪些情况会导致JVM内存泄露

JVM内存泄露通常由以下情况导致&#xff1a; 1. 未释放的对象引用 静态集合类&#xff1a;静态集合&#xff08;如HashMap、ArrayList&#xff09;持有对象引用&#xff0c;导致对象无法被回收。缓存未清理&#xff1a;缓存中的对象未及时清除&#xff0c;长期占用内存。 2.…...

蓝桥杯K倍区间(前缀和与差分,取模化简)

输入 5 2 1 2 3 4 5 输出 6 思路&#xff1a;首先由连续子串和可以想用前缀和&#xff0c;由于加减法总和取模和分别取模结果不受影响&#xff0c;所以我们前缀和之后直接取模方便观察性质&#xff0c;本题前缀和&#xff1a;1&#xff0c;3&#xff0c;6&#xff0c;10&#…...

2025上半年还可以参加那些数学建模竞赛?

数学建模比赛每年有20多场&#xff0c;各大比赛的含金量究竟如何&#xff1f;哪些是真正的国赛&#xff1f;如何选择合适的数学建模竞赛&#xff1f;今天将为你全面解析&#xff0c;从竞赛简介、主办单位、竞赛级别、竞赛时间、报名费用、参赛人员、奖项设置、综合难度、竞赛含…...

网易日常实习一面面经

1. 自我介绍 2. 两道代码题&#xff1a; 第一道题&#xff1a;写一道链表排序题要求空间复杂度O(1) &#xff1a;已ac 插入排序算法 时间复杂度 O(N^2)&#xff0c;空间复杂度O(1) class ListNode{int val;ListNode next;public ListNode(int x) {this.val x;} } public cl…...

Excel 笔记

实际问题记录 VBA脚本实现特殊的行转列 已知&#xff1a;位于同一Excel工作簿文件中的两个工作表&#xff1a;Sheet1、Sheet2。 问题&#xff1a;现要将Sheet2中的每一行&#xff0c;按Sheet1中的样子进行转置&#xff1a; Sheet2中每一行的黄色单元格&#xff0c;为列头。…...

Python的

& 运算符可用于不同集合类型&#xff0c;它主要用于集合的交集操作 下面分别介绍它在 set&#xff08;集合&#xff09;和 frozenset&#xff08;不可变集合&#xff09;这两种常见集合类型中的使用 set 类型 set 是 Python 中内置的可变集合类型&#xff0c;使用 & …...

【个人开发】cuda12.6安装vllm安装实践【内含踩坑经验】

1. 背景 vLLM是一个快速且易于使用的LLM推理和服务库。企业级应用比较普遍&#xff0c;尝试安装相关环境&#xff0c;尝试使用。 2. 环境 模块版本python3.10CUDA12.6torch2.5.1xformers0.0.28.post3flash_attn2.7.4vllm0.6.4.post1 2.1 安装flash_attn 具体选择什么版本&…...

ASP.NET Core SignalR身份验证

在需要登录才能访问的集线器类上或者方法上添加[Authorize]。也支持角色等设置&#xff0c;可以设置到Hub或者方法上。 配置好User、Role、MyDbContext、JWTSettings、IdentityHelper Program.cs using SignaIR的基本使用; using Scalar.AspNetCore; using Identity框架; us…...

微信小程序(第一集)

app.json {// 定义小程序的所有页面路径&#xff0c;数组中的第一个页面是首页"pages": ["pages/index/index", // 首页"pages/logs/logs" // 日志页面],// 设置小程序的全局窗口外观&#xff08;比如导航栏和背景颜色&#xff09;"wind…...

为什么细胞是圆的?

从受力方面分析 以细胞重心 O O O为原点&#xff0c;建立平面直角坐标系 x O y xOy xOy&#xff0c; x 、 y x、y x、y正半轴交细胞于A&#xff0c;B 设 f θ ∑ ∀ P ∈ C , ∠ P O A θ P O ∑ ∀ P ∈ C , ∠ P O A θ 1 f_\theta\dfrac{\sum_{\forall P\in C\ \ , \an…...

游戏引擎学习第96天

讨论了优化和速度问题&#xff0c;以便简化调试过程 节目以一个有趣的类比开始&#xff0c;提到就像某些高端餐厅那样&#xff0c;菜单上充满了听起来陌生或不太清楚的描述&#xff0c;需要依靠服务员进一步解释。虽然这听起来有些奇怪&#xff0c;但实际上&#xff0c;它反映…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...