惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
云风的 BLOG
云风的 BLOG
Microsoft Security Blog
Microsoft Security Blog
博客园 - 司徒正美
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
小众软件
小众软件
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
MyScale Blog
MyScale Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
A
About on SuperTechFans
宝玉的分享
宝玉的分享
WordPress大学
WordPress大学
B
Blog RSS Feed
G
Google Developers Blog
量子位
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)

博客园 - 编写人生

关于数据隔离 Pool 模型 的补充 数据流动、隔离粒度与一致性 大型 ERP 云架构的隔离与协作 企业级 Agent 架构的真实形态:Skill、A2A 与 MCP 的边界思考 如何更优雅地提供 MCP Resources 面向 AI Agent 的能力模型与多协议生成架构设计思考 从 LangGraph 回到 Model-Tool Loop:更聪明的模型,正在让 Agent 架构重新变简单 Agent 的“上下文” 生命周期的思考 .NET GC设计的新想法 AI OS 海量 MCP 工具场景下的上下文隔离选择方案 Python 异步编程完全指南 软件架构设计模式全览 我花了一上午,帮一个 AI 找到了自己的"亲生父母" Agent构建:声明式优于硬编码 探索 Nim 中的 sequtils 与箭头语法 —— 立即计算与惰性计算的那些事 .NET Exception.Data 属性:上下文错误处理的架构深思 组合优于继承最佳实践指南 Nim 枚举类型 对性能的影响 Nim 概念 Concept 对性能的影响 C# 泛型编译特性对性能的影响 在AI大爆发的背景下,企业管理软件有什么冲击 c#使用内存映射像处理内存一样去快速处理文件 关于.net 和 JAVA 在内存处理(GC)设计上的差距(ChatGPT水文) 我与 ChatGPT 讨论了面向对象语言 中,关于动态调用的问题 寓言故事:超喜欢的玩具 关于 智能红绿灯 的简单探索 Nim 语言新的性能测试 Kotlin 实现类似 C# 的 Event 事件代码
UI界面设计:继承 vs 组合 的架构思考
编写人生 · 2025-06-13 · via 博客园 - 编写人生

UI界面设计:继承 vs 组合 的架构思考

在传统的面向对象 UI 框架中,控件往往通过继承基类来扩展功能。例如经典的 WinForms 中,新控件通常继承自 Control 类。但这种继承策略容易导致深层次的类层次,难以灵活扩展。为了解决这一问题,各平台的后续框架逐渐引入了组合优于继承的思想:通过组合小型组件或模板来构建复杂界面,提升灵活性和可复用性。下面按平台分析主流框架的设计思路及演进规律。

经典桌面框架:WinForms 与 WPF

  • WinForms(.NET):WinForms 基于 Windows 传统消息循环,将 UI 控件设计为 Control 基类及其子类。新控件通常继承已有控件或重写 Control,以复用已有功能。但当需要复合功能时,常用 组合 方式:将多个基础控件(如 TextBoxButtonListBox 等)放入一个容器(Form 或 UserControl),形成一个新的复合控件。例如下拉框就是由文本框、按钮和列表等控件组成的。

  • WPF (.NET):WPF 提供了两种创建控件的模型。其一,UserControl(组合已有控件):开发者在 XAML 中把多个标准控件组合起来,实现简单复用和封装;但此种方式不能使用数据模板(DataTemplate)或控件模板(ControlTemplate)进行外观定制。其二,Custom Control(继承):通过继承 Control 类创建控件,控件的外观由 ControlTemplate 定义,从而彻底分离逻辑与表现,支持主题和样式定制。也就是说,WPF 既保留了继承扩展的能力,又通过模板和依赖属性等机制引入了更灵活的组合方式(比如在 XAML 中组合控件或使用附加属性),以克服纯继承的笨重和不灵活。

例如,微软文档指出:派生自 UserControl 的控件 是将现有组件添加到该用户控件中(通过 XAML 组合多个控件),但无法使用 DataTemplate/ControlTemplate 定制外观;而派生自 Control 的自定义控件则使用模板定义外观,实现逻辑与视觉的彻底解耦。
这意味着 WPF 鼓励使用控件组合(UserControl)进行快速开发,同时为需要高度可定制外观的场景提供继承+模板机制。

iOS/macOS 平台:UIKit 与 SwiftUI

  • UIKit/AppKit(传统 Cocoa 框架):UIKit (iOS) 和 AppKit (macOS) 中,视图对象都是从 UIView/NSView 继承而来。但在应用层面,这些框架更倾向于组合而非盲目继承。以 iOS 为例,苹果框架常通过委托(Delegate)和数据源(DataSource)模式来扩展功能,而不是子类化视图。例如,如果只想让 UITextField 限制某些输入字符,正确做法不是继承 UITextField,而是设置一个符合 UITextFieldDelegate 协议的委托对象,由文本框在输入时回调检查;同样,UITableView 不鼓励继承出 SongsTableView 这样的子类,而是通过组合一个数据源对象(通常是控制器)来向表格提供数据。正如开发者 Rob Napier 所言:“UIKit 中继承很少用到组合更常见”。UIKit 的大多数功能都通过标准视图和组件组合实现,只有在需要新增底层功能时才会考虑继承。例如开发者常用的 UIViewController 也是一个组合型的构造器,它把多个视图对象组合起来管理界面和交互。

  • SwiftUI(声明式框架):苹果于 2019 年推出 SwiftUI,一个面向所有 Apple 平台的声明式 UI 框架。在 SwiftUI 中,一切都是 View 协议的实例,界面通过函数组合来构建,而不是通过类继承。开发者可以通过 VStackListNavigationView 等容器,将简单视图(如 TextImage组合成复杂界面。直接说明:“SwiftUI 提供用于声明 App 用户界面的视图…通过 stacks、lists 等将…SwiftUI 视图组合起来”。这种组合方式意味着 UI 的结构更灵活,可读性更高,也更易测试。相比之下,UIKit 依赖的继承和 Interface Builder 链接(IBOutlet/IBAction)繁琐、易出错,而 SwiftUI 通过功能强大的“修饰符”和数据绑定等机制,以组合思想解决了可复用性和状态管理问题。简单来说,UIKit 的核心设计思想是继承,而 SwiftUI 的核心设计思想是组合(声明式)

Android 平台:传统视图体系 vs Jetpack Compose

  • 经典 Android View:Android 最初将所有 UI 元素都设计为继承自同一个 View 基类。Button、TextView、ImageView、ViewGroup 等都是 View 的子类,布局通过嵌套 ViewGroup 实现。这种设计最初方便代码重用——比如按钮不用各自实现文本渲染逻辑。然而,这种 深度继承 的策略很快显露出缺陷:当需求变化时,很难预测最佳的类层次结构。例如,如果要让按钮显示图片而非文字,Android 提供了 ImageButton,但 ImageButtonButton 虽名称相近却没有公用的扩展接口,经常出现调用无效的问题。更广泛地说,当框架要修改已有视图功能时,没有机制通知所有继承自它的子类去检查变化是否兼容,导致难以维护。正如 Louis Tsai 所述:“构建基于继承的 UI 随空间和时间的增长而失效”。

  • Jetpack Compose(声明式 UI):为了解决传统 View 系统的局限,Android 推出了 Jetpack Compose,一个基于 Kotlin 的声明式 UI 框架。在 Compose 中,界面通过组合函数构建:开发者调用各种内置或自定义的 Composable 函数(如 Text(), Button(), Column(), Row() 等)来描述界面,系统负责高效渲染。这种方法避免了笨重的类层次结构,让 UI 更可组合、更动态。正如 Louis Tsai 总结的那样,通过组合的方式构建按钮等控件:“假设 Button = 随意提示 + 背景 + 点击回调”(而不是在视图上做文本渲染的继承逻辑)。Jetpack Compose 借鉴了 Flutter、React 等框架的思路,强调组合优于继承:通过把功能细分为可复用的小组件(函数)并嵌套调用,来构建复杂的界面,从而解决了 Android 视图体系“继承难扩展”的问题。

Web 前端:组件化框架

现代 Web 前端框架(如 React、Vue、Angular 等)本质上也是组件化的设计,强调组合而非继承。以 React 为例,其官方文档直接指出:“React 有一个强大的组合模型,我们推荐使用组合而不是继承来在组件间重用代码”。开发者可以在 JSX 中将组件嵌套或者通过 props.children 传递子组件,实现复用和定制界面。Facebook 进一步说明,在他们的经验中,没有任何情况需要构建组件继承层次:组件间的灵活性完全可以通过组合和属性传递来实现。类似地,Angular 也以模块和组件为基础,通常通过依赖注入(services)将功能组装到组件中,而不是通过继承共享逻辑(即使存在基类组件,也经常被建议尽量简化,更多使用组合的方式)。总之,Web UI 领域的主流框架普遍摒弃深继承体系,转而提倡组件组合的开发模式。

游戏引擎:实体-组件架构

在游戏开发领域,实体-组件系统(Entity-Component-System, ECS)是经典的“组合优于继承”设计模式。在 ECS 中,一个游戏对象(Entity)本身不包含任何行为,所有功能都通过附加多个组件(Component)来实现;系统(System)对拥有特定组件的数据集进行统一处理。例如,在 Unity 引擎中,任何 GameObject 默认只有一个 Transform 组件,它表示位置/旋转/缩放。要让这个对象具备渲染、物理碰撞、音频等功能,需要往同一个 GameObject 上添加 Camera、Collider、AudioSource 等组件。Unity 官方手册指出:“没有组件的话,GameObject 只是内存中的一些信息——它在世界中就像不存在一样”;不同的组件“为 GameObject 提供附加功能”。也就是说,Unity 通过组合组件赋予对象能力,而不是通过在类层次中预定义所有可能的类型。更通用地,ECS 理念强调实体的行为由所拥有的组件而非类继承决定。这种模式彻底避免了传统游戏对象(如怪物、道具)间复杂的多层继承,极大增强了灵活性和可扩展性。

总结

各主流 UI 框架都在继承与组合的博弈中逐步摸索出“组合优于继承”的架构范式。早期框架(WinForms、早期 AppKit/UIKit、Android 视图系统等)依赖继承来复用代码,但在实际应用中经常遇到可扩展性差、类层次脆弱的问题。后来随着经验积累,框架设计者引入了更多的组合机制:WPF 用模板(ControlTemplate/DataTemplate)将外观从逻辑中解耦;iOS 通过委托和协议替代子类化;Android 和 Web 则借鉴函数式、声明式思想,用组件或函数来动态构建界面;游戏引擎全面采用组件化实体模型。这些设计进化共同发现并解决了早期“继承体系过于僵化”的问题,形成了如今被广泛认可的更灵活、模块化的 UI 架构