本地TXT数据通用加载器实现:最优设计模式咨询
针对你的场景,我推荐采用策略模式(Strategy Pattern)搭配泛型抽象基类,再结合一个全局工厂类的组合方案——这既能彻底解决当前单例模式带来的逻辑冗余、依赖过重问题,又能轻松支持未来新增的模型。下面具体拆解实现思路:
核心设计逻辑
首先,你的现有问题根源在于:每个模型的单例都重复实现了「文件读取、逐行处理」这类通用逻辑,同时把「模型解析、验证」这类特有逻辑和通用逻辑耦合在一起。我们需要把这两部分拆分开,让通用逻辑只写一次,特有逻辑按需实现。
1. 抽象基类封装通用加载流程
先创建一个泛型抽象基类DataLoader<T>,把所有模型都需要的通用逻辑(读取文件、跳过空行/表头、字段拆分)封装在这里,只把模型特有的「验证规则」和「字段转对象」逻辑抽象成抽象方法,交给子类实现:
public abstract class DataLoader<T> { // 通用文件加载逻辑:所有模型共享 public List<T> Load(string filePath) { var items = new List<T>(); if (!File.Exists(filePath)) return items; var lines = File.ReadAllLines(filePath); if (lines.Length <= 1) return items; // 跳过表头行 var headerFields = lines[0].Split(';').Select(h => h.Trim()).ToArray(); for (int i = 1; i < lines.Length; i++) { var line = lines[i].Trim(); if (string.IsNullOrWhiteSpace(line)) continue; var entryFields = line.Split(';').Select(f => f.Trim()).ToArray(); // 先验证格式,无效则跳过 if (!IsValidEntry(entryFields, headerFields)) continue; // 转换为对应模型对象 var item = ConvertToModel(entryFields, headerFields); if (item != null) items.Add(item); } return items; } // 抽象方法:子类实现模型特有验证规则 protected abstract bool IsValidEntry(string[] entryFields, string[] headerFields); // 抽象方法:子类实现字段到模型对象的转换 protected abstract T? ConvertToModel(string[] entryFields, string[] headerFields); }
2. 策略模式实现模型特有逻辑
每个模型对应一个DataLoader<T>的子类,只需要专注实现自己的验证和转换逻辑,不用再重复写文件读取的代码。比如Person对应的加载器:
public class PersonLoader : DataLoader<Person> { protected override bool IsValidEntry(string[] entryFields, string[] headerFields) { // 验证Person的字段规则:比如id必须是数字,name和surname不能为空 if (entryFields.Length != headerFields.Length) return false; var idIndex = Array.IndexOf(headerFields, "id"); if (!int.TryParse(entryFields[idIndex], out _)) return false; var nameIndex = Array.IndexOf(headerFields, "name"); var surnameIndex = Array.IndexOf(headerFields, "surname"); return !string.IsNullOrWhiteSpace(entryFields[nameIndex]) && !string.IsNullOrWhiteSpace(entryFields[surnameIndex]); } protected override Person? ConvertToModel(string[] entryFields, string[] headerFields) { var idIndex = Array.IndexOf(headerFields, "id"); var nameIndex = Array.IndexOf(headerFields, "name"); var surnameIndex = Array.IndexOf(headerFields, "surname"); return new Person { Id = int.Parse(entryFields[idIndex]), Name = entryFields[nameIndex], Surname = entryFields[surnameIndex] }; } }
Role和Task的加载器同理,只需要编写对应模型的验证和转换代码即可,代码量极少且逻辑清晰。
3. 工厂类管理全局实例与缓存
如果需要全局访问加载后的数据,可以创建一个静态工厂类DataLoaderFactory,用字典缓存每个模型的加载器实例和已加载的数据,替代单例的全局访问功能,同时更灵活:
public static class DataLoaderFactory { private static readonly Dictionary<Type, object> _loaderCache = new(); private static readonly Dictionary<Type, object> _dataCache = new(); // 获取指定模型的加载器实例 public static DataLoader<T> GetLoader<T>() { var modelType = typeof(T); if (!_loaderCache.ContainsKey(modelType)) { // 自动查找对应加载器(命名约定:ModelName + Loader) var loaderType = Type.GetType($"{modelType.Namespace}.{modelType.Name}Loader"); if (loaderType == null) throw new InvalidOperationException($"No loader found for model {modelType.Name}"); _loaderCache[modelType] = Activator.CreateInstance(loaderType)!; } return (DataLoader<T>)_loaderCache[modelType]; } // 获取指定模型的缓存数据(自动加载如果未缓存) public static List<T> GetCachedData<T>(string filePath) { var modelType = typeof(T); if (!_dataCache.ContainsKey(modelType)) { var loader = GetLoader<T>(); _dataCache[modelType] = loader.Load(filePath); } return (List<T>)_dataCache[modelType]; } }
4. 实际使用方式
在需要数据的地方,只需要一行代码就能获取全局缓存的模型数据:
// 获取Person数据(自动加载并缓存) var persons = DataLoaderFactory.GetCachedData<Person>("Persons.txt"); // 获取Role数据 var roles = DataLoaderFactory.GetCachedData<Role>("Roles.txt");
方案优势
- 消除冗余:通用逻辑只实现一次,每个模型只关注自己的特有规则,代码复用率大幅提升。
- 易于扩展:新增模型时,只需要创建对应的
XxxLoader子类,实现两个抽象方法即可,完全符合开闭原则,不需要修改现有代码。 - 解耦依赖:模型和加载逻辑分离,单例模式带来的依赖过重问题彻底解决。
- 全局访问:通过工厂类的缓存机制,确保数据只加载一次,满足全局访问需求,同时比单例更灵活(比如可以随时清空缓存重新加载)。
额外优化建议
- 可以用属性标记(比如
[MappingField("id")])来标记模型属性对应的字段名,然后通过反射自动完成字段到对象的转换,进一步减少转换代码的冗余。 - 可以添加日志记录,把无效行的内容和原因记录下来,方便后续排查数据格式问题。
内容的提问来源于stack exchange,提问作者doksara
相关产品推荐
相关产品推荐

