【开源】轻量架构(7) ——单例模式框架缓存设计的三次迭代从依赖注入到开箱即用
【开源】轻量架构() ——单例模式框架缓存设计的三次迭代从依赖注入到开箱即用
2026-07-24
作者:周方勇 / 咏方舟-长江支流(金质打印通、用宝框架开源作者)
用宝框架 · 拥抱第一 · 一次书写 · 三端复用
用宝框架是开源轻量级企业级分层架构基架,也是开放的、可扩展的架构体系。.NET Standard 2.0,零依赖,支持 MySQL / SQL Server / Oracle / SQLite并可扩展。SQL就是最好的跨平台语言,可跨语言无缝迁移至鸿蒙 ArkTS 及 Java 技术栈,接口名、类名、方法签名三端完全一致。
用宝架构开发的所有应用,可直接移植到 Java/ArkTS,无需重新设计架构、无需重新分层、无需重新抽象业务逻辑,只需按目标语言语法做形式上的转换。
本文侧重:将缓存类设计为实现缓存提供者接口,应用单例模式,避免使用都忘记DI注册,开箱即用。

一、引言
用宝框架作为一个轻量级、跨语言的开发框架,缓存能力是必不可少的。
但在设计缓存模块时,我经历了三次迭代,从“优雅的依赖注入”到“开箱即用的默认实现”,中间踩了不少坑。
今天把这三次设计的思路和演变过程记录下来,希望对大家有所启发。
二、第一次设计:接口作为成员(依赖注入)
代码示例
public interface ICacheProvider
{
T Get<T>(string key);
void Set<T>(string key, T value, TimeSpan? expiration = null);
void Remove(string key);
void Clear();
}
public class XmlEntityCacheManager
{
private readonly ICacheProvider _cacheProvider; // ← 接口作为成员变量
public XmlEntityCacheManager(ICacheProvider cacheProvider)
{
_cacheProvider = cacheProvider;
}
public T GetOrLoad(string key, Func<string, T> loadFunc)
{
var cached = _cacheProvider.Get<T>(key);
if (cached != null) return cached;
var value = loadFunc(key);
_cacheProvider.Set(key, value);
return value;
}
}
使用方式(必须依赖注入)
// Program.cs 中必须注册
builder.Services.AddSingleton<ICacheProvider, MemoryCacheProvider>();
builder.Services.AddScoped<XmlEntityCacheManager>();
这里出现了第一个问题
很多人不知道要注册 ICacheProvider,或者忘了注册。
我们来看看,如果你是一个普通的开发者,拿到用宝框架想快速跑起来,会怎么做?
你可能直接在控制器里写:
public class ProductController : ControllerBase
{
private readonly XmlEntityCacheManager _cache;
public ProductController(XmlEntityCacheManager cache) // ← 你以为这样就行了
{
_cache = cache;
}
}
运行后,你会看到这样的错误:
System.InvalidOperationException: Unable to resolve service for type 'ICacheProvider'
你一头雾水:“我明明注入了 XmlEntityCacheManager,为什么还报错?”
因为你不知道 XmlEntityCacheManager 还依赖 ICacheProvider,而你没有在 Program.cs 里注册它。
这就是第一个设计的核心问题:把“接口作为成员”,相当于强迫所有使用者都必须理解依赖注入的完整链路,否则程序跑不起来。
为了兼容“忘记注册”的情况,我们加了空缓存降级、加了 _fallbackCache,代码越来越复杂,使用者却越来越困惑。
三、第二次设计:单例 + 成员(伪简化)
为了解决依赖注入的问题,我们想了一个办法:提供一个静态默认实例。
public class XmlEntityCacheManager
{
private readonly ICacheProvider _cacheProvider;
private static XmlEntityCacheManager _default;
public static XmlEntityCacheManager Default
{
get
{
if (_default == null)
{
_default = new XmlEntityCacheManager(new NullCacheProvider()); // ← 默认用空缓存
}
return _default;
}
}
public XmlEntityCacheManager(ICacheProvider cacheProvider)
{
_cacheProvider = cacheProvider;
}
}
问题依然存在
虽然提供了 Default 实例,但内部还是持有一个 ICacheProvider 成员变量。
它只是把“必须依赖注入”变成了“默认用空缓存”,但本质没有变。
使用者还是会遇到:
- 默认用的是
NullCacheProvider,缓存其实不生效 - 想换成真实的缓存,还是得去注册
ICacheProvider - 注册了之后,
Default实例还是空缓存,因为默认实例已经创建了
这个设计只解决了“不报错”的问题,没有解决“好用”的问题。
四、第三次设计:类实现接口 + 默认单例
核心转变
把“接口作为成员”改成“类实现接口”。
public class UserBaoCache<T> : ICacheProvider
{
private readonly DataDictionary<string, T> _cache = new DataDictionary<string, T>();
// ★★★ 默认单例,开箱即用 ★★★
public static UserBaoCache<T> Default { get; } = new UserBaoCache<T>();
// 实现 ICacheProvider 接口
public T Get<T>(string key) { /* 从字典取 */ }
public void Set<T>(string key, T value, TimeSpan? expiration = null) { /* 存入字典 */ }
public void Remove(string key) { /* 从字典删除 */ }
public void Clear() { /* 清空字典 */ }
}
使用方式(零配置)
// 不需要注册任何服务,直接使用
var cache = UserBaoCache<XmlMapEntity>.Default;
var template = cache.GetOrLoad("products", key => LoadFromFile(key));
这就是最终设计:缓存本身就是 ICacheProvider,默认实例就是内存缓存,开箱即用。
如果用户想用 Redis 或其他外部缓存怎么办?
// 用户可以自己实现 ICacheProvider
public class RedisCacheProvider : ICacheProvider { ... }
// 然后注入到自己的服务中
builder.Services.AddSingleton<ICacheProvider, RedisCacheProvider>();
但这是用户的主动选择,不是框架的强制要求。
五、三次设计对比
| 维度 | 设计一(成员+DI) | 设计二(单例+成员) | 设计三(实现接口+默认实例) |
|---|---|---|---|
| 是否必须依赖注入 | ✅ 是 | ❌ 否 | ❌ 否 |
| 默认可用 | ❌ 否 | ⚠️ 可用但缓存不生效 | ✅ 是,直接生效 |
| 用户代码量 | 多(需注册) | 中(可用默认) | 少(直接用) |
| 代码复杂度 | 高 | 中 | 低 |
| 缓存能力 | 外部决定 | 外部决定 | 自带内存缓存 |
六、总结
接口作为成员,是“依赖别人”的思维。
类实现接口,是“自己成为能力”的思维。
设计一个框架的能力时,不要一开始就想着“用户会依赖注入”,要先想着“用户怎么开箱即用”。
用宝框架 UserBaoCache<T> 的原则:
- 默认是内存缓存,零配置可用
- 实现
ICacheProvider接口,保持扩展性 - 提供
Default静态实例,全局共享 - 用户想换缓存,自己实现接口替换即可
简单、直接、少一层判断,往往是最正确的设计。
七、代码
using System;
using UserBaoTech.Foundation.Data;
namespace UserBaoTech.Foundation.Infrastructure
{
/// <summary>
/// 作者:长江支流 2026-7-23
/// 用宝框架默认缓存实现 —— 轻量级内存缓存,开箱即用
/// </summary>
/// <typeparam name="T">缓存值的类型</typeparam>
/// <remarks>
/// <para><b>设计目标:</b></para>
/// <list type="bullet">
/// <item><description>实现 <see cref="ICacheProvider"/> 接口,保持与框架缓存抽象一致</description></item>
/// <item><description>提供 <see cref="Default"/> 静态单例,开箱即用,无需依赖注入</description></item>
/// <item><description>内部使用 <see cref="DataDictionary{TKey, TValue}"/> 存储,轻量高效</description></item>
/// <item><description>支持 Key 前缀隔离,不同模块可独立使用不同实例</description></item>
/// </list>
/// <para><b>使用示例:</b></para>
/// <code>
/// // 直接使用默认单例
/// var cache = UserBaoCache<XmlMapEntity>.Default;
/// var entity = cache.GetOrLoad("products", key => LoadFromFile(key));
///
/// // 或创建独立实例(带 Key 前缀)
/// var reportCache = new UserBaoCache<WebMisControllerCore>("Report_");
/// var controller = reportCache.GetOrLoad("products", key => ParseXml(key));
/// </code>
/// </remarks>
public class UserBaoCache<T> : ICacheProvider
{
private static UserBaoCache<T> _default;
private static readonly object _lock = new object();
/// <summary>
/// 默认单例实例 —— 全局共享,整个应用程序生命周期内只有一个实例
/// </summary>
/// <remarks>
/// 适用场景:
/// - 应用级缓存(如 XML 配置、实体模板等)
/// - 所有用户共享的缓存数据
/// - 不需要依赖注入的简单场景
/// </remarks>
public static UserBaoCache<T> Default
{
get
{
if (_default == null)
{
lock (_lock)
{
if (_default == null)
{
_default = new UserBaoCache<T>();
}
}
}
return _default;
}
}
/// <summary>
/// 内部缓存存储(内存字典)
/// </summary>
private readonly IDataDictionary<string, T> _cache;
private readonly string _keyPrefix;
/// <summary>
/// 无参构造函数(默认无 Key 前缀)
/// </summary>
public UserBaoCache() : this("")
{
}
/// <summary>
/// 构造函数(指定 Key 前缀)
/// </summary>
/// <param name="keyPrefix">Key 前缀,用于隔离不同模块的缓存</param>
public UserBaoCache(string keyPrefix)
{
_cache = new DataDictionary<string, T>();
_keyPrefix = keyPrefix ?? "";
}
/// <summary>
/// 获取或加载缓存项(便捷方法)
/// </summary>
/// <param name="key">缓存键</param>
/// <param name="loadFunc">缓存未命中时的加载函数</param>
/// <param name="expiration">过期时间(可选,默认永不过期)</param>
/// <returns>缓存值</returns>
public T GetOrLoad(string key, Func<string, T> loadFunc, TimeSpan? expiration = null)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("缓存键不能为空", nameof(key));
}
var cacheKey = _keyPrefix + key;
if (_cache.ContainsKey(cacheKey))
{
return _cache[cacheKey];
}
if (loadFunc == null)
{
return default(T);
}
var value = loadFunc(key);
if (value != null)
{
_cache[cacheKey] = value;
}
return value;
}
#region ICacheProvider 接口实现
/// <summary>
/// 从缓存中获取指定键的值
/// </summary>
public TValue Get<TValue>(string key)
{
if (string.IsNullOrEmpty(key))
return default(TValue);
var cacheKey = _keyPrefix + key;
if (_cache.ContainsKey(cacheKey))
{
var value = _cache[cacheKey];
// 尝试将 T 转换为 TValue
try
{
return (TValue)(object)value;
}
catch (InvalidCastException)
{
// 类型不匹配,返回默认值
return default(TValue);
}
}
return default(TValue);
}
/// <summary>
/// 将值存入缓存
/// </summary>
public void Set<TValue>(string key, TValue value, TimeSpan? expiration = null)
{
if (string.IsNullOrEmpty(key))
return;
// 只允许存储 T 类型的值(或可转换为 T 的值)
if (value is T typedValue)
{
var cacheKey = _keyPrefix + key;
_cache[cacheKey] = typedValue;
}
else
{
// 尝试转换
try
{
var converted = (T)(object)value;
var cacheKey = _keyPrefix + key;
_cache[cacheKey] = converted;
}
catch (InvalidCastException)
{
// 类型不匹配,忽略存储
}
}
}
/// <summary>
/// 从缓存中移除指定键
/// </summary>
public void Remove(string key)
{
if (string.IsNullOrEmpty(key))
return;
var cacheKey = _keyPrefix + key;
if (_cache.ContainsKey(cacheKey))
{
_cache.Remove(cacheKey);
}
}
/// <summary>
/// 检查缓存中是否存在指定键
/// </summary>
public bool Exists(string key)
{
if (string.IsNullOrEmpty(key))
return false;
var cacheKey = _keyPrefix + key;
return _cache.ContainsKey(cacheKey);
}
/// <summary>
/// 清空所有缓存
/// </summary>
public void Clear()
{
_cache.Clear();
}
#endregion
}
}
欢迎拷贝、转载,无需授权
反馈与交流:欢迎在评论区留言或私信交流。如果你正在寻找一套 不用 EF、不用 Dapper、可跨语言迁移 的轻量级基架,用宝框架或许是一个值得尝试的选择。
用宝框架,拥抱第一 · 一次书写 · 三端复用——不仅是用户的朋友,而且是用户的宝贝
用宝框架开源地址:GitHub / Gitee 搜索「用宝框架」或「XmlORM」官网
系列博文:跨语言·跨平台·跨数据库 —— 用宝框架系列
csdn首发:https://blog.csdn.net/flygoldfish
更多推荐
所有评论(0)