【GIS课堂】

下面先把你的表述,整理成一段简洁、课前启蒙式 的说明,再把关键疑问提出来。最后给你一版聚焦"当前工具"解耦PlantUML,并配上简要讲解。


一、课前启蒙版简介

我们要做的是一款桌面端 GIS / 绘图软件

它的界面不是简单单页,而是典型的中心画板 + 四周功能面板布局:

  • 中间是地图或画布,负责显示和绘制
  • 四周是工具区、图层区、属性区、列表区、状态区等
  • 用户会频繁在这些区域之间切换操作

这类软件的难点,不只是"把图形画出来",而是大量界面元素之间的联动

  • 点、线、面等几何对象要能绘制
  • 选中对象后,属性表要同步显示和编辑
  • 点击列表项,画布要高亮或定位对应要素
  • 切换工具或切换对象时,属性、画布、状态都要清空或重绘
  • 还要考虑样式编辑、删除、撤销、重做、键鼠监听、状态更新
  • 最终数据还要持久化到 PostgreSQL / PostGIS

所以当前阶段最重要的,不是先堆功能,而是先设计好 UI 架构,尤其是:

  • 组件之间如何通信
  • 谁负责状态
  • 谁负责绘制
  • 谁负责属性同步
  • 谁负责工具切换
  • 如何避免 100 个按钮和面板互相直接调用,最后彻底失控

核心思想可以先抓一句话:

不要让按钮、列表、属性表、画布彼此直接耦合,而是通过"当前工具 + 应用状态 + 事件/命令"来解耦。


二、这件事里最难、最不能漏掉的困难点

这些是你这个系统里真正困难的部分,不能漏:

1. 工具切换带来的全局联动

比如从"画多边形"切到"选择工具"时,可能要同时发生:

  • 停止当前绘制
  • 清空临时点
  • 重置鼠标状态
  • 关闭某些面板编辑态
  • 重绘画布
  • 更新按钮高亮
  • 清空或刷新属性表

这说明"当前工具"不是一个简单按钮状态,而是系统行为的入口


2. UI 元素数量巨大,关系复杂

你提到大概有 100 个按钮/工具,还带很多列表和面板。

这类系统最怕:

  • 按钮直接操作画布
  • 列表直接改属性表
  • 属性表直接控制数据库
  • 画布再回头通知按钮

最后会变成网状调用,非常难维护。


3. 几何编辑不是单动作,而是状态机

点、线、面绘制都不是一次点击完成:

  • 点:点击即生成
  • 线:多次点击、双击结束
  • 面:多次点击、闭合结束
  • 编辑:拖拽节点、插点、删点、确认、取消

所以"工具"本质上更像一个交互状态机,不是单纯函数。


4. 属性编辑与图形编辑必须保持一致

要素有两个面:

  • 空间几何
  • 业务属性

修改任一方,另一方的显示和状态都要同步。

例如:

  • 点击列表项 -> 画布高亮
  • 选中画布要素 -> 属性表刷新
  • 改属性 -> 列表刷新
  • 改几何 -> 脏状态更新、可保存状态更新

5. 撤销/重做会贯穿所有操作

绘制、删除、移动、改属性、改样式,都可能要支持撤销/重做。

这意味着系统不能只靠"直接改对象",而要考虑:

  • 命令记录
  • 操作快照
  • 状态回滚

6. 存库不是 UI 直接做的

最终要落 PostgreSQL/PostGIS,但 UI 层不能直接绑数据库细节。

否则以后:

  • 改数据库结构
  • 改事务逻辑
  • 改缓存策略
  • 改离线模式

都会牵一发动全身。


三、你的三个问题,先给简明回答


问题1:100 个按钮/工具,如果用中介者模式,会不会变成上帝类?

会。

如果你把所有按钮、面板、画布、列表、属性表的交互都塞进一个中介者,那它几乎必然变成"上帝类"。

更合理的做法

不是"不要中介者",而是:

  • 不要只有一个总中介者
  • 改成分层拆分

建议拆成这几类:

  1. ToolManager

    • 只负责当前工具切换
    • 不负责布局
    • 不负责数据库
  2. SelectionCoordinator

    • 负责选中要素、列表联动、属性联动
  3. EditorCoordinator

    • 负责绘制/编辑流程
  4. CommandManager

    • 负责撤销/重做
  5. Document/Application State

    • 统一保存当前状态
  6. EventBus / Observer

    • 做广播,不做业务堆积

所以工业上更常见的不是"一个中介者统治一切",而是:

状态中心 + 多协调器 + 命令系统 + 工具对象


问题2:UI 布局代码是否应该写在中介者类中?

不应该。

UI 布局代码应该放在:

  • MainWindow
  • DockPanelLayout
  • SidebarView
  • CanvasView
  • PropertyPanelView

这类视图/容器类里。

中介者/协调器只应该关心:

  • 谁发了什么事件
  • 当前状态怎么变
  • 哪些组件需要刷新

不应该关心:

  • 按钮摆左边还是右边
  • Splitter 比例是多少
  • Dock 面板怎么嵌套

一句话:

布局属于 View,协调属于 Mediator/Coordinator。


问题3:企业级标准架构/解决方案是什么?

这类复杂桌面软件,工业实践通常不是单独靠某一个设计模式,而是组合架构

常见组合

  • MVVM / MVP / MVC 之一作为 UI 基础分层
  • State / Tool 模式 管当前工具
  • Command 模式 管撤销重做
  • Observer / EventBus 管广播更新
  • Repository + Service 管存库
  • Document / Editor Context 管当前文档状态

也就是说,企业级方案不是:

  • "我们用了中介者模式,所以系统就优雅了"

而是:

用分层架构做骨架,用多个设计模式分别解决工具切换、命令撤销、状态同步、界面联动、数据持久化。


四、推荐你现在先建立的核心认知

这类系统最关键的解耦点,不是"按钮"和"画布",而是:

当前工具 CurrentTool

为什么?

因为用户的大多数输入行为都依赖当前工具解释:

  • 鼠标左键点击,在"选择工具"里是选中
  • 在"点工具"里是新增点
  • 在"线工具"里是追加节点
  • 在"面工具"里是追加边界点
  • 在"编辑工具"里可能是拖动控制点

所以:

  • 按钮不直接操作画布
  • 按钮只是切换 CurrentTool
  • 画布收到鼠标事件后,不自己决定业务,而是转发给 CurrentTool

这就是解耦关键。


五、PlantUML:看继承关系 + 看如何通过"当前工具"解耦

下面这版是启蒙阶段够用、又不会太复杂的结构图。

plantuml 复制代码
@startuml
skinparam classAttributeIconSize 0

class MainWindow
class ToolbarView
class LayerListView
class PropertyPanelView
class CanvasView
class StatusBarView

class AppController
class ToolManager
class SelectionCoordinator
class CommandManager
class DocumentContext
class FeatureRepository
class PostgisFeatureRepository
class FeatureService

interface Tool
class SelectTool
class PointTool
class LineTool
class PolygonTool
class EditTool

class Feature
class Geometry
class PointGeometry
class LineGeometry
class PolygonGeometry

class Command
class CreateFeatureCommand
class UpdateFeatureCommand
class DeleteFeatureCommand

Tool <|.. SelectTool
Tool <|.. PointTool
Tool <|.. LineTool
Tool <|.. PolygonTool
Tool <|.. EditTool

Geometry <|-- PointGeometry
Geometry <|-- LineGeometry
Geometry <|-- PolygonGeometry

Command <|-- CreateFeatureCommand
Command <|-- UpdateFeatureCommand
Command <|-- DeleteFeatureCommand

MainWindow *-- ToolbarView
MainWindow *-- LayerListView
MainWindow *-- PropertyPanelView
MainWindow *-- CanvasView
MainWindow *-- StatusBarView

MainWindow --> AppController

AppController --> ToolManager
AppController --> SelectionCoordinator
AppController --> CommandManager
AppController --> DocumentContext
AppController --> FeatureService

ToolManager --> Tool : currentTool
CanvasView --> ToolManager : query currentTool
CanvasView --> DocumentContext
ToolbarView --> ToolManager : switch tool
LayerListView --> SelectionCoordinator
PropertyPanelView --> SelectionCoordinator

SelectionCoordinator --> DocumentContext
SelectionCoordinator --> PropertyPanelView
SelectionCoordinator --> LayerListView
SelectionCoordinator --> CanvasView

FeatureService --> FeatureRepository
FeatureRepository <|.. PostgisFeatureRepository

CommandManager --> Command
CreateFeatureCommand --> FeatureService
UpdateFeatureCommand --> FeatureService
DeleteFeatureCommand --> FeatureService

DocumentContext --> Feature
Feature --> Geometry
@enduml

六、这张图怎么理解

1. 谁继承谁

最重要的继承有三组:

工具继承

  • Tool 是工具接口
  • SelectTool / PointTool / LineTool / PolygonTool / EditTool 都实现它

这意味着不同工具都能用统一方式处理输入。

几何继承

  • Geometry 是抽象几何
  • PointGeometry / LineGeometry / PolygonGeometry 是具体类型

命令继承

  • Command 是操作抽象
  • CreateFeatureCommand / UpdateFeatureCommand / DeleteFeatureCommand 是具体操作

2. 如何通过"当前工具"解耦

这是最关键的:

不好的方式

  • CanvasView 直接知道"现在按钮 A 被点了,所以执行画面"
  • ToolbarView 直接调用 CanvasView.startPolygon()

这样会把工具栏和画布绑死。

好的方式

  • ToolbarView 只告诉 ToolManager:"切换到 PolygonTool"
  • ToolManager 保存 currentTool
  • CanvasView 收到鼠标事件时,只问:
    • "当前工具是谁?"
  • 然后把事件交给当前工具处理

即:

text 复制代码
按钮 -> ToolManager 切换 currentTool
鼠标事件 -> CanvasView -> currentTool 处理

这样按钮和画布就解耦了。


3. 为什么还需要 DocumentContext

因为当前系统一定有很多"当前状态":

  • 当前工具
  • 当前选中要素
  • 当前编辑对象
  • 当前图层
  • 当前鼠标临时点
  • 当前是否脏数据
  • 当前缩放、显示状态

这些状态不能散落在按钮、属性表、画布里。

所以要有统一状态容器:DocumentContext


4. 为什么要有 SelectionCoordinator

因为"选中一个东西"会引起很多联动:

  • 图层列表高亮
  • 属性表刷新
  • 画布显示选中框
  • 状态栏变化

这类联动不要写进 CanvasView,也不要写进 PropertyPanelView

应交给一个专门的协调者。


5. 为什么要有 CommandManager

因为将来你一定会需要:

  • 撤销
  • 重做
  • 批量操作
  • 删除恢复
  • 编辑回滚

所以创建、编辑、删除,都不要只是直接改对象,要尽量走命令。


七、给你一个更贴近工业实践的结论

对于你这个场景,最推荐的不是"纯中介者模式",而是下面这个组合:

建议架构组合

  • MVVM / MVP:做 UI 分层
  • Tool 模式 + State 思想:做当前工具切换
  • Coordinator/Mediator(多个):做面板联动
  • Command 模式:做撤销重做
  • Repository + Service:做 PostgreSQL/PostGIS 持久化
  • DocumentContext:做统一状态中心

八、建议你下一步优先明确的疑问

下面这些问题非常关键,建议你在设计前先回答:

1. "当前工具"到底只管绘制,还是也管选择/编辑?

也就是说:

  • 选择工具是否也属于 Tool
  • 节点编辑是否也属于 Tool
  • 样式编辑是否算工具态

这个边界要先定。


2. 属性表编辑是"实时提交"还是"确认后提交"?

这会影响:

  • 撤销模型
  • 脏状态判断
  • 数据库存储时机
  • UI 联动复杂度

3. PostgreSQL/PostGIS 保存是:

  • 每次改动立即落库
  • 还是先在内存文档里编辑,最后统一保存

工业上通常更偏向:

先改内存文档,确认后统一保存

否则 UI 和数据库耦合会太深。


4. 列表、属性表、画布,谁是"真相源"?

通常应该只有一个真相源:

  • DocumentContext / EditorState

而不是:

  • 列表存一份
  • 属性表存一份
  • 画布再维护一份

5. 100 个按钮里,有多少是"工具",多少是"命令"?

这两个必须分开:

  • 工具:会持续影响后续鼠标行为

    • 选择、画点、画线、画面、编辑节点
  • 命令:一次性动作

    • 删除、保存、撤销、重做、导入、导出

这是架构拆分的重要前提。


如果你愿意,我下一步可以继续直接给你:

  1. 一版更贴近企业项目的分层架构图
  2. 一版更细的 PlantUML 时序图
    • 比如"点击工具按钮 -> 画布点击 -> 属性联动 -> 命令入栈 -> 保存"的全过程
  3. 一版类职责清单
    • 每个类只写"它该做什么 / 不该做什么"
      这样你就能直接拿去做 UI 架构设计。
相关推荐
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,部署MongoDB(24)
数据库·学习·mongodb
m0_716765232 小时前
数据结构--循环链表、双向链表的插入、删除、查找详解
开发语言·数据结构·c++·学习·链表·青少年编程·visual studio
南無忘码至尊3 小时前
Unity学习90天 - 第 5 天 - 阶段小项目
学习·unity·c#·游戏引擎
韩楚风3 小时前
PostgreSQL入门与进阶学习,体系化的SQL知识,完成终极目标高可用与容灾,性能优化与架构设计,以及安全策略
sql·学习·postgresql
亚空间仓鼠3 小时前
Python学习日志(四):实例
开发语言·python·学习
sealaugh323 小时前
react native(学习笔记第二课) 英语打卡微应用(1)-开始构建
笔记·学习·react native
夜瞬3 小时前
NLP学习笔记03:文本分类——从 TF-IDF 到 BERT
笔记·学习·自然语言处理
Fanfanaas3 小时前
Linux 系统编程 进程篇 (二)
linux·运维·服务器·c语言·开发语言·学习
克里斯蒂亚诺·罗纳尔达3 小时前
智能体学习22——智能体间通信(A2A)
人工智能·学习·ai