核心思路是用抽象接口屏蔽具体图形差异,让渲染引擎只认“可绘制对象”。定义统一的 Drawable 接口(Rust trait 或 C++ 抽象基类),仅暴露 draw()、bounds() 等必要方法;通过动态分发(Vec<Box<dyn Drawable>> 或 vector<unique_ptr<Drawable>>)聚合异构图形,新增类型只需实现接口即可无缝集成;渲染循环中遍历调用 draw(),由运行时 vtable 分发;结合资源与状态分离,复用组件提升扩展性。

核心思路是用抽象接口屏蔽具体图形差异,让渲染引擎只认“可绘制对象”,不关心它到底是圆、线还是3D模型。
定义统一的绘图行为契约
所有图形类型必须实现同一个 trait(Rust)或抽象基类(C++/Java),比如 Drawable。这个接口只暴露最必要的方法,例如 draw()、bounds() 或 is_visible()。不暴露内部字段或构造逻辑,避免调用方耦合具体类型。
- Rust 中写成
trait Drawable { fn draw(&self, renderer: &mut Renderer); } - C++ 中对应
class Drawable { public: virtual void draw(Renderer& r) = 0; virtual ~Drawable() = default; }; - 关键点:方法签名要稳定,返回值和参数尽量精简,便于后续扩展
用动态分发聚合异构图形
渲染器内部用统一容器存储不同图形,不按类型分开管理。Rust 用 Vec<Box<dyn Drawable>>,C++ 用 std::vector<std::unique_ptr<Drawable>>。这样添加新图形时,只要实现 Drawable,就能直接塞进容器,无需修改渲染器代码。
- 新增一个五角星图形?只需写
struct Star; impl Drawable for Star { ... },然后renderer.add_shape(Box::new(Star)); - 容器里混着圆形、折线、SVG路径、甚至远程加载的矢量图标,对渲染器来说都一样
- 注意:Rust 的
Box<dyn T>和 C++ 的unique_ptr<T>都承担了类型擦除 + 动态调度的作用
在渲染循环中无差别调用
主渲染流程不再写一堆 if is_circle { ... } else if is_line { ... }。而是遍历容器,对每个元素调用 draw() —— 具体执行哪个版本,由虚表(vtable)或 trait 对象的 vtable 在运行时决定。
- 调用开销极小,现代编译器对虚函数/动态分发做了大量优化
- 支持条件跳过:可在
draw()实现里先检查可见性或裁剪区域,提前 return,比外部判断更内聚 - 如果某类图形需要特殊处理(如文字需字体上下文),把所需上下文作为参数传入
draw(),而非让渲染器持有全局状态
配合资源与状态分离提升扩展性
真正让多态“活”起来的,是把图形数据、样式、变换矩阵等拆成独立组件,绘图行为只负责消费它们。例如:
- 一个
Text图形可能持有一个TextContent和一个TextStyle,draw()方法组合两者并委托给底层文本渲染器 - 一个
Mesh图形把顶点数据交给 GPU 渲染管线,draw()只负责绑定 shader 和提交 draw call - 这样新增图形类型时,复用已有组件,而不是重复造轮子

















