在Cesium開發(fā)中,我們經常需要全局唯一的地球實例(Cesium.Viewer)—— 一方面,地球渲染本身消耗大量資源,多實例容易導致性能問題;另一方面,業(yè)務邏輯(如添加實體、切換圖層)通常需要基于同一個地球上下文操作。單例模式恰好能解決“全局唯一實例+統(tǒng)一訪問入口”的需求,本文就來解析如何用單例模式封裝Cesium,以及背后的設計思路。
一、單例模式與Cesium的適配性
單例模式的核心是“確保一個類只有一個實例,并提供一個全局訪問點”。這與Cesium的使用場景高度匹配:
-
資源唯一性:
Cesium.Viewer初始化時會占用Canvas、WebGL上下文等資源,多實例可能導致沖突; - 操作統(tǒng)一性:業(yè)務中添加圖層、繪制實體等操作,必須基于同一個地球實例才能生效;
- 簡化調用:全局統(tǒng)一入口,避免在不同模塊中重復初始化或傳遞實例。
二、封裝結構解析
一個典型的Cesium單例封裝會包含以下核心模塊,我們通過代碼示例(偽代碼)來拆解:
class CesiumSingleton {
// 1. 私有靜態(tài)變量:存儲唯一實例
static #instance = null;
// 2. 實例屬性:地球實例、容器DOM等
#viewer = null; // Cesium.Viewer實例
#containerId = null; // 地球容器ID
#defaultOptions = { // 默認配置
terrainProvider: Cesium.createWorldTerrain(),
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
}),
// 其他默認配置(如是否顯示控件、動畫等)
animation: false,
timeline: false
};
// 3. 私有構造函數(shù):防止外部new實例
constructor(containerId, options = {}) {
if (CesiumSingleton.#instance) {
throw new Error('Cesium實例已存在,請通過getInstance獲取');
}
this.#containerId = containerId;
this.#initViewer(options); // 初始化地球
}
// 4. 初始化地球實例
#initViewer(customOptions) {
const container = document.getElementById(this.#containerId);
if (!container) {
throw new Error(`未找到ID為${this.#containerId}的容器`);
}
// 合并默認配置與用戶配置
const options = { ...this.#defaultOptions, ...customOptions };
this.#viewer = new Cesium.Viewer(this.#containerId, options);
// 初始化后的默認操作(如隱藏logo、設置初始視角等)
this.#viewer._cesiumWidget._creditContainer.style.display = 'none';
}
// 5. 靜態(tài)方法:獲取唯一實例(核心)
static getInstance(containerId, options) {
if (!CesiumSingleton.#instance) {
// 首次調用時創(chuàng)建實例
CesiumSingleton.#instance = new CesiumSingleton(containerId, options);
}
// 非首次調用時,若傳入新容器ID,可做容錯提示
if (containerId && CesiumSingleton.#instance.#containerId !== containerId) {
console.warn(`Cesium實例已綁定到容器${CesiumSingleton.#instance.#containerId},新容器ID無效`);
}
return CesiumSingleton.#instance;
}
// 6. 封裝常用API:對外提供簡化接口
// 獲取原生Viewer實例(方便調用未封裝的原生方法)
getViewer() {
return this.#viewer;
}
// 添加實體(簡化原生addEntity操作)
addEntity(entityOptions) {
if (!this.#viewer) return null;
return this.#viewer.entities.add(entityOptions);
}
// 切換底圖
setImageryProvider(provider) {
if (!this.#viewer) return;
this.#viewer.imageryLayers.removeAll();
this.#viewer.imageryLayers.addImageryProvider(provider);
}
// 7. 生命周期管理:銷毀實例
destroy() {
if (this.#viewer) {
this.#viewer.destroy();
this.#viewer = null;
}
CesiumSingleton.#instance = null; // 重置單例,允許重新初始化
}
}
三、核心設計思路
1. 確保實例唯一性
- 通過私有靜態(tài)變量
#instance存儲實例,外部無法直接訪問; - 構造函數(shù)
constructor為私有(或通過判斷阻止重復創(chuàng)建),強制通過getInstance獲取實例; -
getInstance方法中判斷實例是否存在:不存在則創(chuàng)建,存在則直接返回,避免重復初始化。
2. 配置與初始化分離
- 預設
#defaultOptions:包含常用默認配置(如地形、底圖、控件顯示狀態(tài)),減少重復代碼; - 支持用戶傳入
customOptions:通過對象合并覆蓋默認配置,兼顧靈活性與規(guī)范性; - 初始化邏輯封裝在
#initViewer私有方法中,包含容器校驗、實例創(chuàng)建、默認操作(如隱藏Cesium logo),確保初始化過程可控。
3. 簡化API調用
- 對外暴露
addEntity、setImageryProvider等常用方法,屏蔽Cesium原生API的復雜性(如viewer.entities.add); - 提供
getViewer方法:當需要調用未封裝的原生功能時(如特殊相機操作),可直接獲取Viewer實例,避免封裝過度導致的局限性。
4. 生命周期管理
-
destroy方法:不僅銷毀Viewer實例釋放資源,還重置單例變量,支持在特殊場景下重新初始化(如頁面切換時銷毀舊地球,返回時重建); - 容錯處理:如重復傳入不同容器ID時給出警告,避免無意識的錯誤操作。
四、使用示例
封裝后,在項目中使用會非常簡潔:
// 首次初始化(傳入容器ID和自定義配置)
const cesiumInstance = CesiumSingleton.getInstance('cesiumContainer', {
infoBox: false // 覆蓋默認配置,隱藏信息框
});
// 在其他模塊中獲取實例(無需重復初始化)
const sameInstance = CesiumSingleton.getInstance();
// 添加一個點實體
cesiumInstance.addEntity({
position: Cesium.Cartesian3.fromDegrees(116, 39, 1000),
point: { color: Cesium.Color.RED, pixelSize: 10 }
});
// 切換底圖為天地圖
cesiumInstance.setImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
url: 'http://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles',
layer: 'img',
style: 'default',
format: 'image/jpeg'
}));
// 頁面銷毀時釋放資源
cesiumInstance.destroy();
五、優(yōu)缺點與適用場景
優(yōu)點:
- 全局統(tǒng)一:避免多實例沖突,所有操作基于同一個地球上下文;
- 簡化使用:封裝后無需重復編寫初始化代碼,降低新人上手成本;
- 便于維護:核心配置和方法集中管理,修改時只需改單例類,無需全局搜索。
缺點:
- 靈活性有限:不適合需要同時顯示多個地球的場景(如左右分屏對比);
- 單例依賴:過度依賴單例可能導致模塊間耦合度升高(可通過注入方式緩解)。
適用場景:
- 絕大多數(shù)單地球場景(如數(shù)字孿生、GIS應用、三維地圖展示);
- 中小型項目,追求開發(fā)效率和簡單性。
六、總結
用單例模式封裝Cesium的核心是“以最小成本解決全局唯一實例問題”:通過限制實例數(shù)量避免資源沖突,通過封裝API降低使用門檻,通過統(tǒng)一配置提升項目規(guī)范性。
如果你的項目不需要多地球實例,這種封裝方式能顯著減少重復代碼,讓團隊更專注于業(yè)務邏輯而非Cesium的基礎配置。當然,實際開發(fā)中可根據需求擴展(如添加事件監(jiān)聽封裝、相機控制簡化等),讓單例類更貼合項目場景。