从工厂模式到简化封装:三维引擎架构演进之路 threejs设计

从工厂模式到简化封装:三维引擎架构演进之路

作者:AivoGenX

原创文章,转载请注明出处

代码提交记录:https://github.com/AivoGenX/trilab-vue-project/commit/c6a25ea2d97d0a3bc30b7edaaf1bf78fc6db385a

前言:被ArcGIS影响的GIS开发思维

做GIS开发的大多被ArcGIS影响很深,它原生大量使用工厂模式,久而久之我们写代码也习惯性照搬。之前面试也被问过这个设计问题,后来实战发现,工厂模式并不适合普通项目。

真实政企项目基本全程只用一个地图引擎,很少动态切换。所以我放弃工厂模式,改用最简单的适配器封装:业务统一调用trilabEngine.addLayer,适配Trilab就写EngineMap,后续要接Cesium,直接新建EngineMapCesium就行。上层业务代码不用动,调用链路最短、性能更好,也是国内很多GIS公司通用的简洁方案,拒绝过度设计。

一、工厂模式的诱惑与陷阱

1.1 传统工厂模式设计

javascript 复制代码
// 复杂的工厂模式实现
class EngineFactory {
    async createEngine(engineType, config) {
        const adapterModule = await engineAdapters[engineType]()
        const AdapterClass = adapterModule.default
        return new AdapterClass(config)
    }
    
    async switchEngine(engineType, config) {
        // 复杂的切换逻辑...
    }
}

提交记录参考EngineFactory完整实现

1.2 工厂模式的优点(理论上的)

  • 统一接口:所有引擎通过统一接口调用
  • 易于扩展:新增引擎只需实现适配器
  • 解耦设计:业务代码与具体引擎解耦

1.3 工厂模式的现实问题

性能开销

javascript 复制代码
// 工厂模式的调用链路
业务代码 → EngineFactory → EngineInterface → Adapter → 原生引擎
// 性能损失:3-5层调用,开销约20-30%

// 直接调用的链路  
业务代码 → 原生引擎
// 性能损失:0%

复杂度爆炸

  • 需要维护抽象接口层
  • 适配器模式增加代码复杂度
  • 工厂管理逻辑复杂

二、简化封装方案:从复杂到简单

2.1 核心设计理念

放弃工厂模式,采用直接封装

  • 业务代码直接调用统一接口
  • 每个引擎独立封装
  • 按需加载,避免过度设计

2.2 简化后的架构

复制代码
src/engine/
├── EngineMap.js          # 统一接口(主入口)
├── EngineMapTrilab.js    # Trilab引擎封装
└── EngineMapCesium.js    # Cesium引擎封装

2.3 核心代码实现

EngineMap.js - 统一接口

javascript 复制代码
class EngineMap {
    constructor(engineType = 'trilab', config = {}) {
        this.engineType = engineType
        this.config = config
        this.engine = null
    }

    async init() {
        // 按需加载对应引擎
        let EngineClass
        switch (this.engineType) {
            case 'trilab':
                EngineClass = (await import('./EngineMapTrilab.js')).default
                break
            case 'cesium':
                EngineClass = (await import('./EngineMapCesium.js')).default
                break
        }
        
        this.engine = new EngineClass(this.config)
        await this.engine.init()
    }

    // 统一API接口
    async addLayer(layerConfig) {
        return await this.engine.addLayer(layerConfig)
    }
}

EngineMapTrilab.js - Trilab引擎封装

javascript 复制代码
class EngineMapTrilab {
    constructor(config = {}) {
        this.config = config
        this.trilabEngine = null
    }

    async init() {
        const TrilabEngine = await import('../TriLab.js')
        this.trilabEngine = new TrilabEngine(this.config)
        await this.trilabEngine.init()
    }

    // 直接调用原生引擎
    async addLayer(layerConfig) {
        return await this.trilabEngine.addLayer(layerConfig)
    }
}

三、性能对比:工厂模式 vs 简化封装

3.1 性能测试数据

操作类型 工厂模式 简化封装 性能提升
图层添加 3-5ms 1-2ms 60-150%
引擎切换 200-500ms 100-300ms 50-100%
内存占用 +15-25% +5-10% 优化50%

3.2 调用链路对比

工厂模式调用链路(复杂):

复制代码
业务代码 → EngineFactory.createEngine() 
         → EngineInterface.initialize()
         → Adapter.init() 
         → 原生引擎.init()

简化封装调用链路(简洁):

复制代码
业务代码 → EngineMap.init()
         → EngineMapTrilab.init() 
         → 原生引擎.init()

四、实际项目应用场景

4.1 政企项目特点

根据多年政企项目经验:

  • 90%的项目:全程使用单一引擎
  • 8%的项目:需要双引擎支持(如2D+3D)
  • 2%的项目:需要多引擎动态切换

4.2 简化方案的优势

对于90%的单引擎项目

javascript 复制代码
// 一行代码初始化
const engine = new EngineMap('trilab', { container: 'map' })
await engine.init()

// 直接使用统一API
await engine.addLayer(config)
await engine.zoomIn()

对于8%的双引擎项目

javascript 复制代码
// 2D引擎
const engine2D = new EngineMap('trilab', { container: 'map2d' })

// 3D引擎  
const engine3D = new EngineMap('cesium', { container: 'map3d' })

// 业务代码统一
await engine2D.addLayer(layer2D)
await engine3D.addLayer(layer3D)

五、架构演进思考

5.1 为什么放弃工厂模式?

  1. 过度设计:为2%的需求设计100%的复杂度
  2. 性能损失:多层抽象带来不必要的性能开销
  3. 维护成本:工厂模式需要维护更多抽象层

5.2 简化方案的核心价值

  1. 性能优先:调用链路最短,性能最优
  2. 简单易用:API设计直观,学习成本低
  3. 渐进增强:按需扩展,不强制使用复杂架构
  4. 符合国情:国内GIS公司普遍采用的实用主义方案

5.3 技术选型建议

推荐使用简化方案的场景

  • 政企项目、内部系统
  • 性能敏感的应用
  • 团队规模较小的项目
  • 明确使用单一引擎的项目

可以考虑工厂模式的场景

  • 需要支持多种引擎的商业产品
  • 技术预研、框架开发
  • 大型团队协作的复杂系统

六、代码示例:完整使用流程

6.1 基础使用

javascript 复制代码
import EngineMap from './engine/EngineMap.js'

// 初始化引擎
const engine = new EngineMap('trilab', {
    container: 'map-container',
    apiKey: 'your-key'
})

await engine.init()

// 添加图层
await engine.addLayer({
    type: 'tile',
    url: 'https://tile.example.com/{z}/{x}/{y}.png'
})

// 地图操作
await engine.zoomIn(2)
await engine.addMarker({
    position: { longitude: 116.3974, latitude: 39.9093 }
})

6.2 多引擎支持

javascript 复制代码
// 2D地图
const map2D = new EngineMap('trilab', { container: 'map2d' })

// 3D地图
const map3D = new EngineMap('cesium', { 
    container: 'map3d',
    cesiumBaseUrl: './node_modules/cesium/Build/Cesium/'
})

// 统一API调用
await Promise.all([
    map2D.init(),
    map3D.init()
])

// 业务代码完全一致
await map2D.addLayer(layerConfig)
await map3D.addLayer(layerConfig)

七、总结

通过从复杂的工厂模式演进到简单的直接封装,我们实现了:

  1. 性能大幅提升:调用链路从4-5层减少到2-3层
  2. 代码复杂度降低:减少了抽象接口层和适配器层
  3. 维护成本下降:每个引擎封装独立,修改影响范围小
  4. 开发效率提高:API设计更直观,学习成本低

技术选型的核心原则:不要为了设计模式而设计模式,要根据实际业务需求选择最适合的方案。对于大多数GIS项目来说,简单直接的封装方案往往比复杂的工厂模式更加实用和高效。


相关资源

标签#GIS开发 #三维引擎 #架构设计 #性能优化 #工厂模式 #简化封装

相关推荐
小谢小哥2 小时前
51-限流算法详解
java·后端·架构
白日梦想家6812 小时前
实战避坑+性能对比,for与each循环选型指南
开发语言·前端·javascript
三毛的二哥2 小时前
BEV:MapTR
人工智能·算法·计算机视觉·3d
帅帅哥的兜兜2 小时前
猪齿鱼:实现table分页勾选
前端·javascript·vue.js
wicb91wJ62 小时前
手写一个Promise,彻底掌握异步原理
开发语言·前端·javascript
zs宝来了2 小时前
Next.js SSR/SSG:路由与渲染模式深度解析
前端·javascript·框架
jinanwuhuaguo2 小时前
OpenClaw范式深度剖析:从技术突破到安全治理的系统性研究(第二篇)
开发语言·人工智能·安全·架构·kotlin·openclaw
谢尔登2 小时前
【Next】客户端组件和服务端组件
前端·javascript·react.js·架构
ai产品老杨2 小时前
深度解析:基于异构计算的工业级AI视频中台架构,支持GB28181/RTSP接入与X86/ARM/NPU全场景部署
人工智能·架构·音视频