我的 UML 学习笔记:结合 Android 实例看懂 4 种常用图表

前言

UML (Unified Modeling Language) 是统一建模语言,它的核心就是图表。使用它可以帮助我们开发者设计和理解系统,也方便向他人阐述系统。

了解更多:(14 条消息) UML科普文,一篇文章掌握14种UML图 - 知乎

接下来,我们以实战的方式来了解 Android 开发中常用的 UML 图,用到的工具是 StarUML。

类图

类图中的六种关系

类图描述了系统中的类(接口)以及它们之间的关系。

在类图中,有以下几种关系:

  • 泛化(Generalization):是一种继承关系,使用带有空心三角箭头的实线指向父类。

  • 实现(Realization):是类与接口的关系,使用带有空心三角箭头的虚线指向接口。

    BaseActivityIBaseView 接口为例:


    参考代码

    kotlin 复制代码
      interface IBaseView {
          fun initView()
          fun initData()
      }
    
      abstract class BaseActivity : IBaseView {
          fun showToast(s: String) {}
      }
    
      class MainActivity : BaseActivity() {
          override fun initView() {}
          override fun initData() {}
      }
  • 依赖(Dependency):是临时使用的关系,一个类使用到了另一个类,使用带箭头的虚线指向被使用者。

    Handler 发送消息为例:

  • 关联(Association):是一种拥有的关系,即一个类知道另一个类的属性和方法,使用带箭头的实线指向被拥有者。

    注意:它和依赖很容易搞混,依赖强调的是临时使用(形式是方法参数 / 局部变量 / 静态方法调用),而关联强调长期持有(即作为类的成员持有引用)。

    比如 Message 内部会保存 target (Handler),这就是关联关系。

    这里的箭头是 Message 指向 Handler。此外,关联可以是双向的,使用双向箭头的实线连接。

  • 聚合(Aggregation):是一种整体与部分的关系,部分可以单独存在,也是关联关系的一种。在整体的一端是空心菱形,实线连接部分

  • 组合(Composition):也是整体与部分的关系,但部分不能离开整体而单独存在,它也是关联关系的一种。在整体的一端是实心菱形,实线连接部分

    例如:Fragment 可以脱离 Activity 存在,Activtiy 销毁,Fragment 对象不一定立即被销毁;ViewGroup 销毁,子 View 没有依附,也会销毁,不能同时加到另一个父 ViewGroup 。

    关键在于生命周期是否受整体的掌控。

结合 RecyclerView 实战解析

现在,我们以 RecyclerView 相关类为例,巩固一下前面学到的知识。

kotlin 复制代码
data class ItemBean(val title: String)

class NewsSource {
    private val items = mutableListOf<ItemBean>()

    fun addItem(item: ItemBean) {
        items.add(item)
    }

    fun getAll(): List<ItemBean> = items.toList()
}

interface IViewHolder<T> {
    fun bind(data: T)
}

interface IAdapter<T> {
    fun setData(list: List<T>)
}


class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), IViewHolder<ItemBean> {
    val tvTitle: TextView = itemView.findViewById(android.R.id.text1)
    override fun bind(data: ItemBean) {
        tvTitle.text = data.title
    }
}

class MyNewsAdapter(
    private val newsSource: NewsSource?
) : RecyclerView.Adapter<NewsViewHolder>(), IAdapter<ItemBean> {
    private val dataList: MutableList<ItemBean> = mutableListOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(android.R.layout.simple_list_item_1, parent, false)
        return NewsViewHolder(view)
    }

    override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        val item = dataList[position]
        holder.bind(item)
    }

    override fun getItemCount(): Int = dataList.size

    @SuppressLint("NotifyDataSetChanged")
    override fun setData(list: List<ItemBean>) {
        dataList.clear()
        dataList.addAll(list)
        notifyDataSetChanged()
    }

    fun refreshFromSource() {
        newsSource?.let {
            setData(it.getAll())
        }
    }
}

class MainActivity : AppCompatActivity() {

    private val originData = List(3){
        ItemBean("News $it")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val newsRecyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        newsRecyclerView.layoutManager = LinearLayoutManager(this)


        val newsSource = NewsSource().apply {
            repeat(10) {
                addItem(ItemBean("News $it from NewsSource"))
            }
        }

        val newsAdapter = MyNewsAdapter(newsSource)
        newsAdapter.setData(originData)
        newsRecyclerView.adapter = newsAdapter

        lifecycleScope.launch {
            delay(2000.milliseconds)
            val newList = originData.toMutableList().apply {
                add(ItemBean("News 4"))
                add(ItemBean("News 5"))
            }
            newsAdapter.setData(newList)

            delay(3000.milliseconds)
            newsAdapter.refreshFromSource()
        }
    }
}

布局:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

关于泛化和实现,我就不说了,因为一眼看得出,无非就是继承和实现。

接着看依赖关系,NewsViewHolder 依赖了 ItemBean,因为它是 bind 方法的参数 ,如果它的结构改变,NewsViewHolder 也要跟着变。又比如 MainActivity onCreate 方法中的 NewsSource、MyNewsAdapter、ItemBean 局部变量,都是它所依赖的。

最后是最难辨别的关联关系,找很容易,成员变量就是了。其中 val tvTitle: TextViewNewsViewHolder 的成员变量,所以 NewsViewHolder 关联 TextView

val title: StringItemBean 的成员变量,所以 ItemBean 关联 Stringval originData: List<ItemBean>MainActivity 的成员变量,所以 MainActivity 关联 List<ItemBean>

然后 MyNewsAdapter 中的 newsSource,也是关联关系,不过因其是从外部传入的,生命周期不受 MyNewsAdapter 的控制,所以属于特殊的聚合关系

MyNewsAdapter 中的 dataList 也是关联关系,因为是内部直接创建的,当自己被清理时,这个实例也会死亡,所以是组合关系NewsSource 中的 items 也是组合关系。

怎么辨认 A 和 B 是否平等呢?(是普通还是特殊的关联关系?)

如果 A 可以知道 B,B 也可以知道 A,那么就属于平等关系,不存在谁包含谁。例如 NewsViewHolder 和 TextView,ViewHodler 只是借用 TextView 来保留了组件引用,方便自己更新组件文字,ViewHodler 不由 TextView 组成,TextView 也不归 NewsViewHolder 管理。

UML 图:

在标准的 UML 类图规范中,通常不会把基本数据类型和内置的基础类画出来,这会导致整张图变乱,失去展示核心业务和架构的意义。

像一些成员可以直接放到类框的属性栏里,也可以表达关系,同时可以保持图的整洁。

总结一下这六种关系的强弱关系:泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖,关系越强,耦合度往往越高。

序列图 (时序图)

它也被称为时序图,主要用于以时间顺序描述多个对象之间如何协作。

以 Activity 启动为例:当我们调用 startActivity() 时,底层其实经历了复杂的跨进程调用。以核心的对象为例,来了解交互顺序:

  1. MainActivity 发起启动请求。
  2. Instrumentation 拦截请求,并通过 Binder 发送给系统服务 ActivityManagerService (AMS)。
  3. AMS 检查完权限和栈信息后,通知应用进程的 ApplicationThread
  4. ApplicationThread 通过 Handler 切换到主线程,最终由 ActivityThread 真正执行新 Activity 的 onCreate

通常在阅读源码或业务开发中,想要知道某个操作经历了哪些步骤,回调是怎么传的,会画序列图来清晰地展示。

用例图

用例图根本不关心代码,只想知道"谁"在使用系统的"什么"功能,用来描述整个系统的功能,它是从用户视角出发的。

这里有几个核心概念:

  • 参与者 (Actor):一个小人,代表着使用者。
  • 用例 (Use Case):一个椭圆,代表一个可以使用的具体功能。

以新闻业务为例:

  • 用户拥有"浏览新闻列表"、"查看新闻详情"这两个核心用例。

  • 同时可以在详情页中点赞、收藏,"点赞新闻"、"收藏新闻"就是扩展用例(<<extend>>)。

  • 而无论是点赞还是收藏,都必须先完成登录,故"用户登录"就是一个被包含的公共用例(<<include>>)。

活动图

我们之前画的流程图,其实在 UML 标准规范中对应的是活动图,描述了具体业务的实现流程。

比如加载新闻列表数据的逻辑:

  1. 进入新闻列表页面。
  2. 首先检查本地是否有缓存数据。
  3. 如果有缓存,直接渲染列表 UI。
  4. 如果没缓存,通过 NewsSource 发起网络请求获取数据。
  5. 获取成功后保存到本地,同时汇合去渲染 UI。
  6. 流程结束。

可以用活动图画出来:

相关推荐
heimeiyingwang1 小时前
【架构实战】OpenTelemetry链路追踪实战:从零到生产的完整指南
架构
mldong1 小时前
用 DDD 设计工作流引擎:聚合根与充血模型
java·架构
candyTong1 小时前
Claude Code 如何恢复一段会话
后端·架构·ai编程
怣疯knight1 小时前
快速判断apk有没有支持16kb页面标准
android
风流 少年2 小时前
Spring AI 2.0:Advisor
android·人工智能·spring
YM52e4 小时前
分页查询的基石:ArkTS 为鸿蒙商品列表设计 LIMIT/OFFSET 的表
android·学习·华为·harmonyos
段一凡-华北理工大学5 小时前
AI推动工业智能化转型~系列文章20:工业 AI 平台架构:云-边-端协同的技术体系
人工智能·python·架构·工业平台·云-边协同
CodeBlog-star5 小时前
Harness Engineering:Pi Agent 架构深度解析
人工智能·python·架构·harness工程
IT大白鼠5 小时前
OpenBao开源密钥管理系统:技术架构、核心功能与行业应用研究
架构·开源·aiops·openbao