一种不太正经的单例写法

125 阅读1分钟

单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心目的是确保全局访问一个类只有一个实例。通常用于需要严格控制某些资源的访问权限或确保全局状态一致性的场景。

实现的方式有很多种:使用类构造函数立即执行函数模块模式闭包版本等等

个人比较常用的是类构造函数

export default class User {
    static instance = null
    static getInstance() {
       if(!User.instance) User.instance = new User()
       return User.instance
    }
    list = []
    constructor() {
    }

    add(name) {
       this.list.add(name)
    }
}

调用方式

import User from './manager/user.js'
User.getInstance().add('jack')

这种方式的好处是简单明了,使用成本低。但是在需要多个单例下每个都要去写一遍比较麻烦。之前在使用一些web后端框架时,controller层和service层的类都会被自动收集实例化,如下图:

image.png

尝试着在前端环境下,也是否可以做到这样自动收集类和并且只实例化一次,代码如下:

//收集工作目录下的类文件
const modules = import.meta.glob('/src/inkeson/*.js', {eager: true})
//缓存全局的类实例
const instanceMap = new Map()
export function createInkeson() {
  //遍历类文件进行,进行实例化缓存实例
      Object.values(modules).forEach((value) => {
         const cls = value.default
         const instance = new cls()
         if(!instanceMap.get(cls.name)) instanceMap.set(cls.name, instance)
      })
    // 注册vue插件
    const inkeson = {
       _map: instanceMap,
       install(app)
          app.config.globalProperties.$inkeson = inkeson
          inkeson._app = app
       },
    }
    return inkeson
}

// 暴露使用方式
export function useInstance (name) {
    return instanceMap.get(name)
}

创建一个User类

export default class User {
    list = []

    constructor() {
    }

    add(name) {
       this.list.add(name)
    }
}

注册vue插件:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import {createInkeson} from "../package/inkeson/index.js";
const inkeson = createInkeson()


createApp(App)
    .use(inkeson)
    .mount('#app')

使用:

import {useInstance} from "../package/inkeson/index.js";

const userInstance = useInstance('User');
userInstance.add('jack')

每次使用需要引入useInstance方法比较麻烦,我们可以粗暴一点直接把实例注入到根节点上,改造一下代码:

//收集工作目录下的类文件
const modules = import.meta.glob('/src/inkeson/*.js', {eager: true})
//缓存全局的类实例
const instanceMap = new Map()
export function createInkeson() {
    // 注册vue插件
    const inkeson = {
       _map: instanceMap,
       install(app){
          //遍历类文件进行,进行实例化缓存实例
          Object.values(modules).forEach((value) => {
             const cls = value.default
             const instance = new cls()
             if(!instanceMap.get(cls.name)) instanceMap.set(cls.name, instance)
             // 通过provide将类实例注入到vue组件中
             app.provide(cls.name,instance)
          })
          app.config.globalProperties.$inkeson = inkeson
          inkeson._app = app
       },
    }
    return inkeson
}

// 暴露使用方式
export function useInstance (name) {
    return instanceMap.get(name)
}

注意:cls.name 读取的是类名,太过简单,实际使用需要有一定的规则避免命名冲突

多一种使用方式:

import {useInstance} from "../package/inkeson/index.js";
import {inject} from "vue";

const userInstance = useInstance('User');
userInstance.add('jack')

const userInstance2 = inject('User');
userInstance2.add('tom')

如需新增一个单例管理类,只需要在工作目录下[inkeosn]新增一个类即可