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: Retrofit

    init {
        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 {
    @TimeLog
    fun testMethod() {
        Thread.sleep(500)  // 模拟耗时操作
    }
}
//📌 使用反射 + AOP 拦截
fun logExecutionTime(obj: Any) {
    val methods = obj::class.java.declaredMethods
    for (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 Activity
        Log.d("AOP", "Activity ${activity::class.java.simpleName} - onCreate")
    }
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】深入理解linux权限

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、权限是什么 二、用户和身份角色 三、文件属性 1. 文件属性表示 2. 文件类型 3. 文件的权限属性 四、修改文件的权限属性和角色 1. …

三次握手,四次挥手,服务器模型(多进程并发,线程),基于套接字的UDP通信

三次握手&#xff1a; 第一次握手&#xff1a;客户端向服务器发送SYN待确认数据x, 客户端进入SYN_SEND状态​ 第二次握手&#xff1a;服务器向客户端回传一条ACK应答数据x1, 同时发送一条SYN待确认数据y&#xff0c;服务器进入SYN_RECV状态​ 第三次握手&#xff1a;客户端向服…

PostgreSQL的学习心得和知识总结(一百六十七)|深入理解PostgreSQL数据库之静态语法检查工具PgSanity的使用和实现

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库链接,点击前往 4、日本著名PostgreSQL数据库专家 铃木启修 网站…

【数据结构】双向链表(真正的零基础)

链表是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过指针的链接来实现的&#xff01;在上篇我们学习了单向链表&#xff0c;而单向链表虽然空间利用率高&#xff0c;插入和删除也只需改变指针就可以达到&#xff01;但是我们在每次查找、删除、访问..…

Docker 之mysql从头开始——Docker下mysql安装、启动、配置、进入容器执行(查询)sql

一、Docker 之mysql安装配置 步骤一&#xff1a;拉取镜像 1. 查看是否包含已安装的mysql。 docker images | grep mysql 2. 如上图所示&#xff0c;我们有mysql镜像&#xff0c;所以不必对mysql镜像进行拉取&#xff0c;如若没有上图中的惊喜&#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…

DeepSeek LLM 论文解读:相信长期主义开源理念可扩展大语言模型(DeepSeek 吹响通用人工智能的号角)

论文链接&#xff1a;DeepSeek LLM: Scaling Open-Source Language Models with Longtermism&#xff08;相信长期主义开源理念可扩展大语言模型&#xff09; 目录 摘要一、数据处理&#xff08;一&#xff09;数据清洗与丰富&#xff08;二&#xff09;分词器与词汇设置 二、模…

02DevOps基础环境准备

准备两台Linux的操作系统&#xff0c;最简单的方式就是在本机上使用虚拟机搭建两个操作系统&#xff08;实际生产环境是两台服务器&#xff0c;虚拟机的方式用于学习使用&#xff09; 我搭建的两台服务器的ip分别是192.168.1.10、192.168.1.11 192.168.1.10服务器用于安装doc…

基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发(文末联系,整套资料提供)

基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发 一、系统介绍 随着人们生活水平的提高和健康意识的增强&#xff0c;智能健康监测设备越来越受到关注。智能腰带作为一种新型的健康监测设备&#xff0c;能够实时采集用户的腰部健康数据&#xff0c;如姿势、运动…

表单与交互:HTML表单标签全面解析

目录 前言 一.HTML表单的基本结构 基本结构 示例 二.常用表单控件 文本输入框 选择控件 文件上传 按钮 综合案例 三.标签的作用 四.注意事项 前言 HTML&#xff08;超文本标记语言&#xff09;是构建网页的基础&#xff0c;其中表单&#xff08;<form>&…

vue3中使用print-js组件实现打印操作

第一步&#xff1a;安装依赖 yarn add print-js 第二步&#xff1a;创建打印组件&#xff1a;PrintHtmlComp.vue <template><div id"printArea_123456789"><!-- 默认插槽&#xff0c;传入打印内容 --><slot></slot></div>…

【计算机网络】TCP/IP 网络模型有哪几层?

目录 应用层 传输层 网络层 网络接口层 总结 为什么要有 TCP/IP 网络模型&#xff1f; 对于同一台设备上的进程间通信&#xff0c;有很多种方式&#xff0c;比如有管道、消息队列、共享内存、信号等方式&#xff0c;而对于不同设备上的进程间通信&#xff0c;就需要网络通…

网络工程师 (29)CSMA/CD协议

前言 CSMA/CD协议&#xff0c;即载波监听多路访问/碰撞检测&#xff08;Carrier Sense Multiple Access with Collision Detection&#xff09;协议&#xff0c;是一种在计算机网络中&#xff0c;特别是在以太网环境下&#xff0c;用于管理多个设备共享同一物理传输介质的重要…

基于Python的人工智能驱动基因组变异算法:设计与应用(下)

3.3.2 数据清洗与预处理 在基因组变异分析中,原始数据往往包含各种噪声和不完整信息,数据清洗与预处理是确保分析结果准确性和可靠性的关键步骤。通过 Python 的相关库和工具,可以有效地去除噪声、填补缺失值、标准化数据等,为后续的分析提供高质量的数据基础。 在基因组…

AI大语言模型

一、AIGC和生成式AI的概念 1-1、AIGC Al Generated Content&#xff1a;AI生成内容 1-2、生成式AI&#xff1a;generative ai AIGC是生成式 AI 技术在内容创作领域的具体应用成果。 目前有许多知名的生成式 AI&#xff1a; 文本生成领域 OpenAI GPT 系列百度文心一言阿里通…

在postman中设置环境变量和全局变量以及五大常用响应体断言

一、什么是环境变量和全局变量 环境变量&#xff08;Environment Variables&#xff09;和全局变量&#xff08;Global Variables&#xff09;是 Postman 中用于存储和管理数据的两种变量类型&#xff0c;它们可以提高 API 测试的灵活性和可维护性。 1、 环境变量&#xff08…

Redis数据库(二):Redis 常用的五种数据结构

Redis 能够做到高性能的原因主要有两个&#xff0c;一是它本身是内存型数据库&#xff0c;二是采用了多种适用于不同场景的底层数据结构。 Redis 常用的数据结构支持字符串、列表、哈希表、集合和有序集合。实现这些数据结构的底层数据结构有 6 种&#xff0c;分别是简单动态字…

C++STL(六)——list模拟

目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…

国产编辑器EverEdit - 替换功能详解

1 替换 1.1 应用场景 替换文本是在文档编辑过程中不可回避的操作&#xff0c;是将指定的关键词替换为新的文本&#xff0c;比如&#xff1a;写代码时修改变量名等。 1.2 使用方法 1.2.1 基本替换 使用主菜单查找 -> 替换&#xff0c;或使用快捷键Ctrl H&#xff0c;会打…

LIMO:上海交大的工作 “少即是多” LLM 推理

25年2月来自上海交大、SII 和 GAIR 的论文“LIMO: Less is More for Reasoning”。 一个挑战是在大语言模型&#xff08;LLM&#xff09;中的复杂推理。虽然传统观点认为复杂的推理任务需要大量的训练数据&#xff08;通常超过 100,000 个示例&#xff09;&#xff0c;但本文展…