Angular组件/服务级DI实现策略模式:Local与Session Storage服务设计
浏览器LocalStorage/SessionStorage存储服务的优秀设计实践
你现有的StorageService已经实现了JSON序列化/反序列化的核心逻辑,要扩展支持LocalStorage,下面针对你的方案和更优实践逐一分析:
方案1:抽象基类+子类实现(推荐)
这种方案完全符合单一职责原则和开闭原则,每个子类只负责对应存储类型的实例化,通用逻辑全部抽离到抽象基类,扩展性极强——后续如果要添加自定义存储(比如带过期时间的存储),只需要新增子类即可。
具体实现代码
// 抽象基类:定义通用存储逻辑 export abstract class AbstractStorageService { protected abstract storage: Storage; public retrieve<T>(key: string): T | null { const item = this.storage.getItem(key); if (!item || item === 'undefined') { return null; } try { return JSON.parse(item) as T; } catch (e) { console.error(`解析存储项${key}失败`, e); this.remove(key); // 解析失败时清理无效数据 return null; } } public store<T>(key: string, value: T): void { try { const serialized = JSON.stringify(value); this.storage.setItem(key, serialized); } catch (e) { console.error(`存储项${key}失败`, e); } } public remove(key: string): void { this.storage.removeItem(key); } public clear(): void { this.storage.clear(); } } // LocalStorage子类 @Injectable({ providedIn: 'root' }) export class LocalStorageService extends AbstractStorageService { protected storage: Storage = localStorage; } // SessionStorage子类 @Injectable({ providedIn: 'root' }) export class SessionStorageService extends AbstractStorageService { protected storage: Storage = sessionStorage; }
优势
- 职责清晰:每个服务只对应一种存储类型,调用方无需额外判断
- 类型安全:通过泛型
T避免any类型,提升代码可靠性 - 可扩展:新增存储类型只需继承抽象类,无需修改原有逻辑
- 错误处理:新增JSON解析/序列化的异常捕获,避免崩溃并清理无效数据
方案2:单一服务多方法实现
这种方案的优点是无需新增类,但缺点非常明显:
- API臃肿:需要为每种存储类型重复定义
storeLocal/storeSession、retrieveLocal/retrieveSession等方法,维护成本高 - 违反单一职责:一个服务同时处理两种存储逻辑,后续扩展其他存储时会导致方法爆炸
- 调用繁琐:调用方需要明确指定使用哪种存储,容易出现调用错误
因此不推荐这种方案。
额外优化方案:依赖注入动态指定存储
如果希望在同一个服务中灵活切换存储类型,可以通过构造函数注入Storage实例,结合Angular的依赖注入系统配置不同的提供者:
实现代码
@Injectable({ providedIn: 'root' }) export class StorageService { constructor(private storage: Storage) {} public retrieve<T>(key: string): T | null { // 同抽象基类的retrieve逻辑 } public store<T>(key: string, value: T): void { // 同抽象基类的store逻辑 } // 其他方法... } // 在模块中配置不同的提供者 @NgModule({ providers: [ { provide: Storage, useValue: localStorage }, // 或者根据需要提供SessionStorage: // { provide: Storage, useValue: sessionStorage }, ] }) export class AppModule {}
优势
- 灵活性高:通过修改依赖注入配置,无需修改业务代码即可切换存储类型
- 复用性强:核心逻辑集中在一个服务中,避免代码重复
通用优秀实践
除了上述架构设计,还需注意以下细节:
- 统一键名管理:用枚举定义所有存储键,避免硬编码,比如
export enum StorageKeys { USER_INFO = 'user_info', THEME = 'theme' } - 容量限制处理:浏览器存储通常有5MB的限制,可以在
store方法中添加容量检查,避免存储失败 - 过期时间支持:如果需要存储带过期时间的数据,可以在序列化时添加
expires字段,retrieve时检查是否过期并清理 - 避免存储敏感数据:LocalStorage/SessionStorage都是明文存储,不要存放密码、token等敏感信息(敏感信息建议用HttpOnly Cookie)
内容的提问来源于stack exchange,提问作者Mihai Socaciu
相关产品推荐
相关产品推荐

