一、什么是设计模式?
设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。设计模式(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
创建单例是有效的,因为:
- 枚举的实例是唯一的:
INSTANCE
只会在RetrofitManager
类加载时创建一次,符合单例模式的定义。
- 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
)
}
}
有一说一,我这个示例,很简陋,能力有限,在需求开发中在这种场景使用过。
🔹 优势:
- 桥接不同的数据结构,避免 API 数据格式和 UI 组件直接绑定,提升代码灵活性。
- 让已有代码无需修改,只需更改适配器,保持项目稳定。
- 提高代码复用性,适配器可以被多个 UI 组件或业务模块复用。
7. 代理模式(Proxy Pattern)
作用:为对象提供一个代理类,控制对原对象的访问。
📌 场景:
- 动态权限申请(ActivityCompat.requestPermissions)
- Retrofit 动态代理
- AOP(Aspect-Oriented Programming)例如retrofit的日志拦截,埋点等,AOP(面向切面编程)主要是基于代理模式实现的,但它不仅仅局限于代理模式,还可以结合字节码操作(如 ASM、Javassist)和编译器插桩(AspectJ)等方式。
AOP 本质上就是通过代理模式,在不修改原代码的情况下,动态地增强功能(如日志、权限控制、监控等)。
在 AOP 的实现中,通常使用 动态代理(JDK 动态代理、CGLIB 代理) 来拦截方法调用,并在方法执行前后执行额外的逻辑。
📌 代理模式核心思路:
- 静态代理:预先定义代理类,手动编写代理逻辑(适用于少量接口)。
- 动态代理:运行时生成代理对象,动态添加方法增强(适用于大规模 AOP)。
- 字节码操作:直接修改
.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")
}
}
常用的三方库里面很多都是基于上述的设计模式去实现的,我自己一般可能就只会用到前面四个,哈哈哈