news 2026/9/1 1:26:06

组合模式实战:统一处理树形结构对象的设计模式详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
组合模式实战:统一处理树形结构对象的设计模式详解

在开发中,我们常常需要处理一种“部分-整体”的层次结构,例如文件系统中的文件夹与文件、公司组织架构中的部门与员工、UI界面中的容器与控件。当我们需要以统一的方式处理单个对象和由这些对象组成的组合对象时,如果采用传统的条件分支判断(如if (obj instanceof File) ... else if (obj instanceof Directory) ...),代码会迅速变得臃肿且难以维护。组合模式(Composite Pattern)正是为解决这类问题而生的经典结构型设计模式,它允许你将对象组合成树形结构,并且能像处理单个对象一样处理整个树结构。

本文将从实际场景出发,深入剖析组合模式的核心思想、UML类图,并提供Java、Python等多种语言的完整实现示例。无论你是正在完成设计模式课程大作业的学生,还是希望在Spring框架或Qt C++项目中应用此模式提升代码质量的开发者,都能通过本文掌握从理论到实战的全流程。我们将重点讲解如何避免常见误区,并分享在真实项目中的最佳实践。

1. 组合模式的核心概念与价值

1.1 什么是组合模式?

组合模式是一种结构型设计模式,它通过将对象组合成树形结构来表示“部分-整体”的层次结构。该模式的核心在于,它使得客户端可以统一地对待单个对象(叶子节点)和组合对象(容器节点),无需关心当前处理的是单个对象还是整个组合结构。

专业定义(GoF《设计模式》):将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

1.2 解决了什么问题?

在没有组合模式的情况下,客户端代码通常需要明确知道它正在处理的是单个对象还是一个对象集合。这会导致大量的类型检查和条件分支,使得代码:

  1. 违反开闭原则:每增加一种新的组件类型,都需要修改客户端代码。
  2. 可读性差:业务逻辑被淹没在繁琐的对象类型判断中。
  3. 难以复用:处理组合结构的逻辑无法直接用于处理单个对象。

组合模式通过定义一个统一的抽象组件接口,让容器和叶子都实现该接口,从而消除了客户端代码中的差异。客户端只需面向该抽象接口编程,极大地简化了代码。

1.3 典型应用场景

组合模式在软件开发中无处不在,以下是一些经典场景:

  • 图形用户界面(GUI):窗口、面板、按钮、文本框等组件可以嵌套。一个容器(如Panel)可以包含其他容器或基本控件,整个界面就是一棵组件树。
  • 文件系统:目录可以包含文件或其他目录。对目录进行“计算大小”或“查找”操作,需要递归地处理其所有子项。
  • 组织架构:公司由部门组成,部门又由子部门或员工组成。计算整个公司的成本或传达通知,需要遍历整个架构。
  • 菜单系统:一个菜单可以包含子菜单或菜单项。渲染整个菜单或处理点击事件需要统一对待。
  • XML/JSON文档解析:元素节点可以包含文本节点或其他元素节点。

理解这些场景有助于我们把握组合模式的精髓:用一致的方式处理具有层次结构的同类型对象

2. 组合模式的角色与UML类图

要深入理解组合模式,必须厘清其参与的几个关键角色。下面我们结合UML类图和详细说明来解析。

2.1 模式中的角色

  1. 组件(Component)

    • 定义:这是一个抽象类或接口,它为组合中的所有对象(包括叶子节点和容器节点)声明了公共接口。它通常包含一些默认行为(如增加、删除、获取子组件)的管理方法,这些方法对于叶子节点可能没有实际意义(可抛出异常或提供空实现)。
    • 作用:定义统一的访问接口,是模式能够统一处理的关键。
  2. 叶子(Leaf)

    • 定义:表示组合中的叶子节点对象。叶子节点没有子节点。
    • 作用:实现组件接口中定义的行为。对于管理子组件的方法(如addremove),叶子节点通常不提供支持或给出错误提示。
  3. 容器(Composite)

    • 定义:表示包含子组件的容器节点。它实现了组件接口,并持有一个子组件对象的集合(如List)。
    • 作用:实现组件接口中定义的行为,尤其是那些需要委托给其子组件来完成的业务操作(如operation())。同时,它负责管理其子组件的生命周期(增加、删除、获取)。

2.2 UML类图解析

+----------------------+ | <<Interface>> | | Component | +----------------------+ | + operation(): void | | + add(c: Component) | (可选) | + remove(c: Component)| (可选) | + getChild(i: int) | (可选) +----------------------+ / \ / \ implements / \ implements / \ +----------------+ +------------------+ | Leaf | | Composite | +----------------+ +------------------+ | - state | | - children: List<Component> | +----------------+ +------------------+ | + operation() | | + operation() | | | | + add(c) | | | | + remove(c) | | | | + getChild(i) | +----------------+ +------------------+ | | contains v +----------------------+ | [Component Objects] | +----------------------+

类图关键点说明

  • Component是核心,它定义了operation()这个核心业务方法。add,remove,getChild这些管理子组件的方法也被定义在这里,这使得客户端可以透明地调用,但需要在叶子节点中妥善处理(例如抛出UnsupportedOperationException)。
  • Leaf实现了operation()方法,执行具体的、不涉及子组件的操作。
  • Composite除了实现operation()方法,还维护了一个子组件列表。它的operation()方法通常会遍历所有子组件,并调用它们的operation()方法,从而实现递归操作。
  • 透明式 vs 安全式:上图展示的是“透明式”组合模式,即将所有方法(包括管理子组件的方法)都定义在Component中。另一种变体是“安全式”,它只在Composite中定义管理子组件的方法。我们将在后续详细比较两者的优劣。

3. 环境准备与示例说明

在开始编码前,我们需要明确示例的环境和背景。本文将使用一个贴近实际的例子:模拟一个简单的图形绘制系统。在这个系统中,有基本的图形(如圆、正方形)和容器(如图形组),容器可以包含基本图形或其他容器。我们可以对单个图形或整个图形组执行“绘制”操作。

环境说明

  • 编程语言:主示例使用Java,因其在面向对象和设计模式教学中应用最广。同时,我们会在关键部分提供Python的实现对比,以满足不同技术栈读者的需求。
  • 开发环境:任何标准的IDE或文本编辑器均可(如 IntelliJ IDEA, Eclipse, VS Code)。
  • 项目结构:一个简单的Java项目,无需额外依赖。

版本建议

  • Java: JDK 8 或以上版本(使用Lambda表达式会更简洁)。
  • Python: 3.6 或以上版本。

我们的目标是构建一个可运行的示例,让你能直观感受组合模式如何简化客户端代码。

4. 透明式组合模式完整实现(Java)

“透明式”组合模式将组合对象和叶子对象的接口完全统一,所有方法都定义在抽象组件中。这是最经典、教科书式的实现。

4.1 定义抽象组件(Component)

首先,我们创建Graphic接口,它声明了所有图形对象共有的操作。

// 文件路径:src/main/java/com/example/composite/Graphic.java /** * 抽象组件(Component):图形接口 * 声明所有图形对象(包括简单图形和组合图形)的公共操作。 */ public interface Graphic { /** * 核心业务方法:绘制图形 */ void draw(); /** * 添加子图形(对于叶子图形,此操作无意义) * @param graphic 要添加的子图形 */ void add(Graphic graphic); /** * 移除子图形(对于叶子图形,此操作无意义) * @param graphic 要移除的子图形 */ void remove(Graphic graphic); /** * 获取指定索引的子图形(对于叶子图形,此操作无意义) * @param index 子图形索引 * @return 子图形对象 */ Graphic getChild(int index); }

代码解释:这里定义了四个方法。draw()是核心业务方法。add,remove,getChild是用于管理子组件的方法。在透明式中,它们被放在接口里,意味着叶子对象也必须实现它们,尽管它们对叶子对象没有实际意义。

4.2 实现叶子组件(Leaf)

接下来,我们实现两种简单的叶子图形:CircleSquare

// 文件路径:src/main/java/com/example/composite/Circle.java /** * 叶子组件(Leaf):圆形 */ public class Circle implements Graphic { private String name; public Circle(String name) { this.name = name; } @Override public void draw() { // 模拟绘制圆形的操作 System.out.println("绘制圆形: " + name); } @Override public void add(Graphic graphic) { // 叶子节点不支持添加操作,抛出运行时异常 throw new UnsupportedOperationException("叶子图形不支持添加操作"); } @Override public void remove(Graphic graphic) { throw new UnsupportedOperationException("叶子图形不支持移除操作"); } @Override public Graphic getChild(int index) { throw new UnsupportedOperationException("叶子图形没有子图形"); } }
// 文件路径:src/main/java/com/example/composite/Square.java /** * 叶子组件(Leaf):正方形 */ public class Square implements Graphic { private String name; public Square(String name) { this.name = name; } @Override public void draw() { // 模拟绘制正方形的操作 System.out.println("绘制正方形: " + name); } @Override public void add(Graphic graphic) { throw new UnsupportedOperationException("叶子图形不支持添加操作"); } @Override public void remove(Graphic graphic) { throw new UnsupportedOperationException("叶子图形不支持移除操作"); } @Override public Graphic getChild(int index) { throw new UnsupportedOperationException("叶子图形没有子图形"); } }

关键点:叶子类实现了所有接口方法。对于管理子组件的方法,我们选择抛出UnsupportedOperationException。这是一种明确的错误提示方式,告知客户端这些操作对叶子对象是非法的。也可以选择空实现({}),但那样会隐藏错误,不推荐。

4.3 实现容器组件(Composite)

现在,我们实现容器组件CompositeGraphic,它可以包含其他Graphic对象。

// 文件路径:src/main/java/com/example/composite/CompositeGraphic.java import java.util.ArrayList; import java.util.List; /** * 容器组件(Composite):组合图形 * 可以包含其他简单图形或组合图形。 */ public class CompositeGraphic implements Graphic { private String name; // 用于存储子组件的集合 private List<Graphic> childGraphics = new ArrayList<>(); public CompositeGraphic(String name) { this.name = name; } @Override public void draw() { // 1. 先绘制容器本身(例如,绘制一个边框) System.out.println("开始绘制图形组: " + name); // 2. 递归地绘制所有子图形 for (Graphic graphic : childGraphics) { graphic.draw(); // 关键:这里统一调用子组件的draw方法,无需判断类型 } System.out.println("结束绘制图形组: " + name); } @Override public void add(Graphic graphic) { childGraphics.add(graphic); } @Override public void remove(Graphic graphic) { childGraphics.remove(graphic); } @Override public Graphic getChild(int index) { if (index >= 0 && index < childGraphics.size()) { return childGraphics.get(index); } return null; } // 可选:提供一个获取所有子图形的方法 public List<Graphic> getChildGraphics() { return new ArrayList<>(childGraphics); // 返回副本以保护内部状态 } }

代码解释

  • childGraphics列表用于存储所有子组件。
  • draw()方法是组合模式威力的体现:它先执行容器自身的逻辑(打印开始信息),然后遍历所有子组件并调用它们的draw()方法。由于Graphic接口的统一性,这里无需知道子组件是CircleSquare还是另一个CompositeGraphic。如果是另一个容器,它的draw()方法会继续递归下去。
  • add,remove,getChild方法提供了对子组件的管理功能。

4.4 客户端代码与运行演示

最后,我们编写客户端代码来组装这棵树并执行操作。

// 文件路径:src/main/java/com/example/composite/Client.java /** * 客户端代码 */ public class Client { public static void main(String[] args) { // 1. 创建叶子图形 Graphic circle1 = new Circle("红色圆"); Graphic circle2 = new Circle("蓝色圆"); Graphic square1 = new Square("绿色方块"); // 2. 创建组合图形(容器) CompositeGraphic group1 = new CompositeGraphic("第一组"); CompositeGraphic group2 = new CompositeGraphic("第二组"); CompositeGraphic rootGroup = new CompositeGraphic("根组"); // 3. 构建树形结构 // 第一组包含一个圆和一个方块 group1.add(circle1); group1.add(square1); // 第二组包含一个圆 group2.add(circle2); // 根组包含第一组和第二组 rootGroup.add(group1); rootGroup.add(group2); // 4. 客户端统一调用:无需知道处理的是单个图形还是复杂组合 System.out.println("--- 绘制单个图形 ---"); circle1.draw(); System.out.println("\n--- 绘制一个图形组 ---"); group1.draw(); System.out.println("\n--- 绘制整个图形树 ---"); rootGroup.draw(); // 5. 演示透明性:客户端可以“尝试”对叶子调用add,但会抛出异常 // System.out.println("\n--- 错误操作演示 ---"); // circle1.add(square1); // 运行时会抛出 UnsupportedOperationException } }

运行结果

--- 绘制单个图形 --- 绘制圆形: 红色圆 --- 绘制一个图形组 --- 开始绘制图形组: 第一组 绘制圆形: 红色圆 绘制正方形: 绿色方块 结束绘制图形组: 第一组 --- 绘制整个图形树 --- 开始绘制图形组: 根组 开始绘制图形组: 第一组 绘制圆形: 红色圆 绘制正方形: 绿色方块 结束绘制图形组: 第一组 开始绘制图形组: 第二组 绘制圆形: 蓝色圆 结束绘制图形组: 第二组 结束绘制图形组: 根组

结果分析:客户端代码rootGroup.draw()一行命令就触发了对整个复杂树形结构的递归绘制。客户端完全不用关心内部是单个图形还是嵌套的组合,这就是组合模式带来的“透明性”好处。

5. 安全式组合模式实现与对比

透明式虽然统一,但让叶子对象拥有了它本不该有的方法(add,remove),这违反了接口隔离原则,可能引发运行时异常。安全式组合模式试图解决这个问题。

5.1 安全式实现思路

在安全式中,管理子组件的方法(add,remove,getChild定义在Composite类中,而不在顶层的Component接口中。

修改后的类图关系

  • Component接口只定义draw()方法。
  • Leaf类实现Component,只需实现draw()
  • Composite类实现Component,并额外拥有管理子组件的方法和列表。

5.2 Java 安全式实现代码片段

// 安全式 Component public interface Graphic { void draw(); } // 安全式 Leaf public class Circle implements Graphic { private String name; public Circle(String name) { this.name = name; } @Override public void draw() { System.out.println("绘制圆形: " + name); } } // 安全式 Composite public class CompositeGraphic implements Graphic { private String name; private List<Graphic> childGraphics = new ArrayList<>(); public CompositeGraphic(String name) { this.name = name; } @Override public void draw() { System.out.println("开始绘制图形组: " + name); for (Graphic graphic : childGraphics) { graphic.draw(); } System.out.println("结束绘制图形组: " + name); } // 只有Composite才有这些方法 public void add(Graphic graphic) { childGraphics.add(graphic); } public void remove(Graphic graphic) { childGraphics.remove(graphic); } public Graphic getChild(int index) { /* ... */ } }

5.3 透明式 vs 安全式 优劣对比

特性透明式组合模式安全式组合模式
接口统一性。客户端完全一致地对待所有对象。。客户端需要判断对象类型才能调用管理方法。
安全性。客户端可能对叶子调用管理方法,导致运行时异常。。从编译期就杜绝了错误调用。
符合接口隔离原则不符合。叶子被迫实现不需要的方法。符合。接口最小化。
客户端代码复杂度低。无需类型判断,代码简洁。高。需要instanceof判断,失去了透明性。
适用场景系统需要高度统一,且能接受叶子节点抛出异常(或提供空实现)。系统更注重类型安全,且客户端愿意处理不同类型的差异。

工程建议:在大多数框架和库中(如Java AWT/Swing的Component/Container),更倾向于使用透明式。因为框架设计者认为,让客户端代码更简单、更统一的价值,大于让叶子节点实现几个空方法(或抛出异常)的代价。在你自己设计的系统中,可以根据团队规范和具体场景进行选择。

6. Python 实现示例

对于使用Python的开发者,组合模式的实现同样清晰。Python的动态特性让实现更加灵活。下面我们实现安全式组合模式。

# 文件:graphic.py from abc import ABC, abstractmethod from typing import List class Graphic(ABC): """抽象组件(Component)""" @abstractmethod def draw(self): pass class Circle(Graphic): """叶子组件(Leaf)""" def __init__(self, name: str): self.name = name def draw(self): print(f"绘制圆形: {self.name}") class Square(Graphic): """叶子组件(Leaf)""" def __init__(self, name: str): self.name = name def draw(self): print(f"绘制正方形: {self.name}") class CompositeGraphic(Graphic): """容器组件(Composite)""" def __init__(self, name: str): self.name = name self._children: List[Graphic] = [] def draw(self): print(f"开始绘制图形组: {self.name}") for child in self._children: child.draw() # 递归调用 print(f"结束绘制图形组: {self.name}") # 管理子组件的方法(安全式,只有Composite有) def add(self, graphic: Graphic): self._children.append(graphic) def remove(self, graphic: Graphic): self._children.remove(graphic) def get_child(self, index: int) -> Graphic: if 0 <= index < len(self._children): return self._children[index] return None # 客户端代码 if __name__ == "__main__": # 创建图形 circle1 = Circle("红色圆") square1 = Square("绿色方块") # 创建组合 group1 = CompositeGraphic("第一组") root = CompositeGraphic("根组") # 构建结构 group1.add(circle1) group1.add(square1) root.add(group1) # 统一操作 print("--- 绘制整个结构 ---") root.draw() # 尝试错误操作(安全式下,叶子没有add方法,会在编码时提示错误) # circle1.add(square1) # AttributeError: 'Circle' object has no attribute 'add'

Python实现特点:使用abc模块定义抽象基类,类型提示使代码更清晰。安全式的优点在Python中同样存在:试图调用叶子对象的add方法会在运行时抛出AttributeError,但更理想的是在静态类型检查(如mypy)阶段就发现错误。

7. 组合模式在真实项目中的应用与变体

理解了基础实现,我们来看看组合模式在更复杂场景下的应用和常见变体。

7.1 支持不同操作的扩展

我们的例子只有draw()操作。在实际项目中,组件接口可能定义多个业务方法。例如,一个UI组件接口可能有draw(),resize(),getExtent()等方法。组合模式的美妙之处在于,只需在Component接口中增加新方法,并在Leaf和Composite中实现(或处理)它,整个对象树就自动支持了新操作,这符合“开闭原则”。

// 扩展的Component接口 public interface Graphic { void draw(); void move(int x, int y); // 新增移动操作 // ... 其他操作 } // 在Leaf和Composite中实现move方法 // Leaf的move只移动自己 // Composite的move需要遍历所有子组件并调用其move方法

7.2 父组件引用

有时,子组件需要知道自己的父容器(例如,在UI中,一个控件可能需要访问其父窗口的属性)。可以在Component类中添加一个指向父Composite的引用,并在Composite.add()方法中设置这个引用。这需要小心管理引用关系,避免循环引用。

7.3 缓存与优化

如果Composite的操作(如计算总大小)开销很大,可以在Composite中引入缓存机制。当子组件发生变化时(add/remove),使缓存失效;当再次请求时,如果缓存有效则直接返回,否则重新计算并缓存。这体现了组合模式与其它模式(如享元模式)结合的可能性。

8. 常见问题与排查思路

在实践中应用组合模式,可能会遇到一些典型问题。下面是一个快速排查指南。

问题现象可能原因解决思路
调用叶子节点的add()方法时程序崩溃或抛出异常使用了透明式组合模式,但客户端未意识到叶子节点不支持该操作。1.预防:在文档中明确说明。2.处理:客户端调用前使用instanceof判断(但这破坏了透明性)。3.改用安全式:从接口设计上杜绝错误调用。
无限递归或栈溢出错误Compositeoperation()方法中,遍历子组件时形成了循环引用(例如,A包含B,B又包含A)。1. 在add()方法中加入检查,禁止将父组件添加到其子组件中。2. 在add()方法中,检查待添加组件是否已经是当前组件树中的祖先节点。
执行组合对象的操作后,部分子对象状态未更新Compositeoperation()方法可能没有正确地递归调用所有子组件的对应方法。1. 检查Composite.operation()中的遍历逻辑,确保遍历了所有子组件。2. 检查子组件列表是否被意外修改。
内存泄漏子组件持有对父组件的强引用,导致整个组件树无法被GC回收。1. 如果父组件引用不是必须的,考虑使用弱引用(WeakReference)。2. 确保在对象不再使用时,正确地从父组件中移除(remove)子组件。
性能问题(深层次树遍历慢)树形结构非常深或非常宽,递归遍历开销大。1. 考虑使用迭代代替递归(利用栈)。2. 引入缓存机制,避免重复计算(如7.3所述)。3. 评估是否可以使用其他数据结构或模式优化。

9. 最佳实践与工程建议

将组合模式成功应用到生产项目,需要注意以下工程细节:

  1. 优先考虑透明式,但需明确约定透明式能带来最简洁的客户端代码,这是其主要优势。如果选择透明式,必须在团队内达成共识:叶子节点对于管理子组件的方法,统一抛出UnsupportedOperationException,并在接口文档中清晰说明。不要让叶子节点静默地忽略这些调用(空实现),那会隐藏Bug。

  2. 组件接口设计应保持稳定Component接口是系统的核心契约。一旦确定,应尽量避免修改,特别是增加新的业务方法。如果必须增加,要考虑所有现有的LeafComposite类是否需要实现,评估影响范围。这体现了面向对象设计中的“接口隔离”和“稳定抽象”原则。

  3. 简化Composite类的实现Composite类通常需要管理一个子对象集合。可以考虑将这部分管理逻辑抽取到一个独立的“子节点管理器”类中,使Composite更专注于业务逻辑的协调。例如,可以有一个ChildrenManager类负责add,remove,getChild以及遍历操作。

  4. 与迭代器模式结合使用组合模式天然产生树形结构。当需要以多种方式遍历这棵树时(如前序、后序、广度优先),可以将迭代器模式(Iterator Pattern)与组合模式结合。让Composite实现一个返回特定迭代器的方法,从而将遍历算法与组件结构分离。

  5. 在Spring框架中的应用思考Spring框架本身大量使用了类似组合模式的思想。例如,ApplicationContext的层次结构、BeanDefinition的合并。在你的业务代码中,如果遇到可以表示为树形结构的配置、权限、菜单等,考虑使用组合模式来设计,能使代码与Spring的哲学更契合。

  6. 明确树的边界与职责在设计阶段就要明确:哪些对象可以作为Leaf,哪些作为Composite?一个常见的误区是将本应是Leaf的对象设计成Composite(例如,一个“文件”对象内部又包含文件列表)。这违反了最小知识原则。确保Leaf是真正的原子操作单元。

组合模式是构建层次化、可扩展对象系统的强大工具。它通过统一接口屏蔽了简单元素和复杂容器的差异,让客户端代码更加简洁和稳定。掌握它,不仅能帮你优雅地完成设计模式大作业,更能让你在构建UI框架、处理配置信息、设计领域模型时多一种精良的武器。建议你根据本文的示例代码动手实现一遍,并尝试将其改造用于模拟一个真实的场景,如公司部门预算计算或游戏场景图渲染,这将极大地加深你的理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 1:26:05

基于微信小程序的社区养老院系统源码+文档+讲解视频

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/1 1:25:35

构建具备自我进化能力的智能体:融合东方智慧与决策成长体系

最近在探索智能体开发时&#xff0c;发现很多框架要么过于复杂&#xff0c;要么缺乏对智能体“成长性”和“决策自主性”的系统设计。一个真正有用的智能体&#xff0c;应该像人一样&#xff0c;能够基于经验学习、自我优化&#xff0c;并在复杂环境中做出明智决策。本文将围绕…

作者头像 李华
网站建设 2026/9/1 1:25:35

网约车司机月入2.5万?先算清流水、成本和真实工作强度

月入2.5万的网约车司机&#xff0c;听起来确实比很多普通岗位的收入都高。但这个数字一旦落到现实里&#xff0c;问题就变多了&#xff1a;这到底是流水还是净收入&#xff1f;每天在线多少小时&#xff1f;车是租的还是自己买的&#xff1f;平台抽成多少&#xff1f;违章、维修…

作者头像 李华
网站建设 2026/9/1 1:23:23

刘海屏适配全攻略:安全区原理与多端实现

去年秋天改版项目的时候&#xff0c;前端群里出现了一句很有意思的话&#xff1a;“你说喜欢海&#xff0c;我以为是我的齐刘海。”大家正在感慨产品经理又改了需求&#xff0c;结果发现这句话其实是从一张移动端 UI 稿里截出来的。UI 设计师的本意是“用户喜欢大海”&#xff…

作者头像 李华
网站建设 2026/9/1 1:23:06

STM32F103+CoreXY步进电机运动控制实现

用STM32F103驱动CoreXY机构的步进电机运动控制&#xff0c;整个过程最考验的不是让电机转起来&#xff0c;而是脉冲控制、加减速规划和结构联动是否稳定。我这次把CoreXY平台的最小系统跑通之后发现&#xff0c;单纯点动很容易&#xff0c;真正有价值的部分是把XY两个轴换算成两…

作者头像 李华
网站建设 2026/9/1 1:22:10

基于SpringBoot的老旧小区改造信息管理系统(程序+文档+讲解)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华