Vue 中使用传统的 JavaScript(非 TypeScript)同样可以基于依赖倒置原则(已实战)
在 Vue 中使用传统的 JavaScript(非 TypeScript)同样可以基于依赖倒置原则(Dependency Inversion Principle, DIP)处理业务逻辑。虽然没有 TypeScript 的接口类型检查,但可以通过 约定式抽象 和 依赖注入 实现解耦。以下是具体实现方法,以 localStorage 的 get/set 操作为例:
1. 核心思路
- 依赖倒置原则:组件不直接依赖具体的存储实现(如
localStorage),而是依赖一个抽象的存储服务接口。 - 实现目标:允许后续更换存储方式(如
sessionStorage、IndexedDB或Mock 存储),而无需修改组件代码。
2. 具体实现步骤
(1) 定义抽象存储服务
约定一个存储服务的“接口”(通过对象方法的形式):
// storage/StorageService.js
export class StorageService {
getItem(key) {
throw new Error("必须实现 getItem 方法");
}
setItem(key, value) {
throw new Error("必须实现 setItem 方法");
}
}
(2) 创建具体实现类
基于抽象约定,实现具体的存储服务(如 localStorage):
// storage/LocalStorageService.js
import { StorageService } from './StorageService';
export class LocalStorageService extends StorageService {
getItem(key) {
return localStorage.getItem(key);
}
setItem(key, value) {
localStorage.setItem(key, value);
}
}
(3) 通过依赖注入使用服务
在 Vue 中通过 provide/inject 将存储服务注入组件:
父组件(提供实现)
// App.vue
import { LocalStorageService } from './storage/LocalStorageService';
export default {
provide() {
return {
// 注入具体实现
storageService: new LocalStorageService()
};
}
}
子组件(使用服务)
// UserComponent.vue
export default {
inject: ['storageService'], // 注入存储服务
methods: {
saveUser(user) {
// 通过抽象方法调用,不直接依赖 localStorage
this.storageService.setItem('currentUser', JSON.stringify(user));
},
loadUser() {
const userData = this.storageService.getItem('currentUser');
return userData ? JSON.parse(userData) : null;
}
}
}
3. 动态切换存储实现
如果需要更换存储方式(例如切换到 sessionStorage),只需修改注入的实现,无需改动组件代码:
// 创建新的实现类
// storage/SessionStorageService.js
import { StorageService } from './StorageService';
export class SessionStorageService extends StorageService {
getItem(key) {
return sessionStorage.getItem(key);
}
setItem(key, value) {
sessionStorage.setItem(key, value);
}
}
// App.vue
import { SessionStorageService } from './storage/SessionStorageService';
export default {
provide() {
return {
storageService: new SessionStorageService() // 切换实现
};
}
}
4. Mock 存储用于测试
在单元测试中,可以注入一个 Mock 存储服务:
// storage/MockStorageService.js
import { StorageService } from './StorageService';
export class MockStorageService extends StorageService {
constructor() {
super();
this.store = {}; // 用内存对象模拟存储
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = value;
}
}
// 测试代码中
const mockStorage = new MockStorageService();
mockStorage.setItem('test', '123');
console.log(mockStorage.getItem('test')); // 输出 "123"
5. 工厂模式统一管理服务
通过工厂函数动态创建存储服务,增强灵活性:
// storage/StorageFactory.js
import { LocalStorageService } from './LocalStorageService';
import { SessionStorageService } from './SessionStorageService';
import { MockStorageService } from './MockStorageService';
export class StorageFactory {
static createStorage(type = 'local') {
switch (type) {
case 'local':
return new LocalStorageService();
case 'session':
return new SessionStorageService();
case 'mock':
return new MockStorageService();
default:
throw new Error('未知的存储类型');
}
}
}
// App.vue
import { StorageFactory } from './storage/StorageFactory';
export default {
provide() {
return {
storageService: StorageFactory.createStorage('local') // 按需创建
};
}
}
6. 优势总结
- 解耦组件与存储细节:组件只依赖
StorageService的抽象方法,不关心具体是localStorage还是其他实现。 - 可扩展性:新增存储方式(如
IndexedDB)只需添加新类并修改注入逻辑。 - 便于测试:测试时注入
MockStorageService,无需操作真实localStorage。 - 代码一致性:所有存储操作通过统一接口调用,避免直接操作
localStorage的散落代码。
7. 完整代码示例
文件结构:
src/
├── storage/
│ ├── StorageService.js # 抽象类
│ ├── LocalStorageService.js # 具体实现
│ ├── StorageFactory.js # 工厂类
└── components/
└── UserComponent.vue # 使用存储服务的组件
组件中使用:
// UserComponent.vue
export default {
inject: ['storageService'],
data() {
return {
user: { id: 1, name: "Alice" }
};
},
methods: {
saveData() {
this.storageService.setItem('user', JSON.stringify(this.user));
},
loadData() {
const data = this.storageService.getItem('user');
this.user = data ? JSON.parse(data) : {};
}
},
mounted() {
this.loadData();
}
}
8. 总结
通过约定抽象类(StorageService)和依赖注入(provide/inject),即使没有 TypeScript,也可以在 Vue 中实现依赖倒置原则。这种方式让代码更灵活、可维护,同时为未来扩展(如更换存储方式或添加缓存逻辑)提供了清晰路径。

浙公网安备 33010602011771号