C#配方管理系统实战:工厂模式与单例模式的黄金组合
在工业软件和餐饮管理系统中,配方管理模块往往是核心功能之一。想象一下这样的场景:一家食品加工厂需要管理数百种产品的配方,每种配方包含原料配比、工艺参数等关键数据;或者一个连锁餐厅的中央厨房,需要确保每家分店都能准确获取最新版的标准食谱。这类系统的共同特点是需要高效、安全地处理配方的生命周期管理,同时保证全局一致性。
传统面向过程的编码方式在这里会遇到明显瓶颈——随着业务复杂度上升,代码会变得难以维护,对象创建逻辑散落在各处,状态管理混乱。这正是设计模式大显身手的时刻。本文将带你用C#实现一个工业级配方管理系统,重点演示如何将工厂模式与单例模式组合使用,解决实际开发中的三大痛点:
- 对象创建标准化:通过工厂模式统一配方对象的创建流程
- 全局访问控制:利用单例模式确保配方管理器的唯一性
- 业务逻辑解耦:接口设计使系统各部分保持独立演进能力
1. 系统架构设计
1.1 核心组件关系
我们先来看整体架构的UML简图(代码实现时将省略绘图相关描述)。系统主要由三个关键部分组成:
- Recipe类:承载配方数据的实体
- IRecipeManager接口:定义管理契约
- RecipeManager实现类:单例模式的核心载体
- RecipeFactory类:工厂模式的体现
// 配方实体类示例
public class Recipe
{
public string Code { get; } // 唯一标识符
public string Name { get; set; }
public Dictionary<string, decimal> Ingredients { get; } // 原料与用量
public string ProcessFlow { get; set; } // 工艺流程说明
public Recipe(string code)
{
Code = code;
Ingredients = new Dictionary<string, decimal>();
}
}
1.2 模式选择依据
为什么选择这两种模式组合?来看个对比表格:
| 设计模式 | 解决的核心问题 | 在本系统中的应用价值 |
|---|---|---|
| 工厂模式 | 对象创建依赖 | 统一配方创建流程,隐藏复杂初始化逻辑 |
| 单例模式 | 全局资源访问 | 确保全系统使用同一个配方管理器实例 |
| 组合优势 | - | 既规范了对象出生,又控制了生命周期 |
实际项目中,我们曾遇到多个模块各自实例化管理器导致数据不一致的问题。改用单例模式后,这类bug彻底消失。
2. 核心代码实现
2.1 单例模式的高级实现
标准的单例实现有很多种,我们选择线程安全且延迟初始化的版本:
public sealed class RecipeManager : IRecipeManager
{
private static readonly Lazy<RecipeManager> _instance =
new Lazy<RecipeManager>(() => new RecipeManager());
private readonly ConcurrentDictionary<string, Recipe> _recipes;
private RecipeManager()
{
_recipes = new ConcurrentDictionary<string, Recipe>();
}
public static RecipeManager Instance => _instance.Value;
// 实现接口方法...
}
关键改进点:
- 使用
Lazy<T>实现真正的延迟初始化 - 采用线程安全的
ConcurrentDictionary替代普通Dictionary - 密封类防止派生破坏单例
2.2 工厂模式的灵活应用
基础工厂模式大家都会,但实际项目中我们需要更灵活的变体:
public static class RecipeFactory
{
public static Recipe Create(string code, string name)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentException("配方编码不能为空");
return new Recipe(code) { Name = name };
}
public static Recipe CreateFromTemplate(string code, Recipe template)
{
var recipe = Create(code, template.Name);
// 深拷贝其他属性...
return recipe;
}
}
工厂方法的优势在这里体现得淋漓尽致:
- 集中校验逻辑
- 支持多种创建方式
- 方便后续扩展(如连接数据库获取模板)
3. 业务功能实现
3.1 增删改查的完整实现
让我们实现完整的IRecipeManager接口:
public interface IRecipeManager
{
Recipe GetRecipe(string code);
bool TryAddRecipe(Recipe recipe);
bool UpdateRecipe(Recipe recipe);
bool DeleteRecipe(string code);
IEnumerable<Recipe> GetAllRecipes();
}
对应的实现要点:
public bool TryAddRecipe(Recipe recipe)
{
return _recipes.TryAdd(recipe.Code, recipe);
}
public bool UpdateRecipe(Recipe recipe)
{
return _recipes.TryUpdate(recipe.Code, recipe, _recipes.GetValueOrDefault(recipe.Code));
}
注意这里使用了原子操作来保证线程安全。实际项目中,我们还需要添加版本控制:
public class Recipe
{
// 新增版本字段
public int Version { get; private set; } = 1;
public void IncrementVersion() => Version++;
}
3.2 典型业务场景处理
在食品行业,配方版本管理至关重要。我们扩展更新逻辑:
public bool UpdateRecipe(Recipe newVersion)
{
if (!_recipes.TryGetValue(newVersion.Code, out var current))
return false;
if (newVersion.Version <= current.Version)
throw new InvalidOperationException("不能回退版本");
newVersion.IncrementVersion();
return _recipes.TryUpdate(newVersion.Code, newVersion, current);
}
4. 高级应用与扩展
4.1 观察者模式集成
当配方变更时需要通知质检系统,我们可以轻松集成观察者模式:
public sealed class RecipeManager : IRecipeManager, IObservable<RecipeChangeEvent>
{
private readonly List<IObserver<RecipeChangeEvent>> _observers = new();
public IDisposable Subscribe(IObserver<RecipeChangeEvent> observer)
{
_observers.Add(observer);
return new Unsubscriber(_observers, observer);
}
private void Notify(RecipeChangeEvent change)
{
foreach (var observer in _observers)
{
observer.OnNext(change);
}
}
private class Unsubscriber : IDisposable
{
private readonly List<IObserver<RecipeChangeEvent>> _observers;
private readonly IObserver<RecipeChangeEvent> _observer;
public Unsubscriber(List<IObserver<RecipeChangeEvent>> observers,
IObserver<RecipeChangeEvent> observer)
{
_observers = observers;
_observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
}
4.2 持久化存储集成
单例模式需要特别注意持久化问题。我们可以抽象存储层:
public interface IRecipeRepository
{
Task SaveAsync(Recipe recipe);
Task<Recipe> LoadAsync(string code);
Task DeleteAsync(string code);
}
// 在RecipeManager中注入存储实现
private readonly IRecipeRepository _repository;
private RecipeManager(IRecipeRepository repository)
{
_repository = repository;
LoadInitialData();
}
5. 实战技巧与陷阱规避
5.1 单元测试策略
测试单例类需要特殊技巧:
[TestFixture]
public class RecipeManagerTests
{
[Test]
public void Instance_Always_ReturnsSameObject()
{
var instance1 = RecipeManager.Instance;
var instance2 = RecipeManager.Instance;
Assert.AreSame(instance1, instance2);
}
[TearDown]
public void TearDown()
{
// 通过反射重置单例实例
typeof(RecipeManager)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)
?.SetValue(null, null);
}
}
5.2 常见陷阱与解决方案
| 陷阱现象 | 根本原因 | 解决方案 |
|---|---|---|
| 多线程下出现重复实例 | 经典单例实现非线程安全 | 使用Lazy或双重检查锁 |
| 配方更新后部分模块仍使用旧数据 | 对象引用被缓存 | 实现变更通知机制 |
| 单元测试相互干扰 | 单例状态持久化 | 测试后重置静态实例 |
在最近的一个制药行业项目中,我们遇到了配方变更后部分设备控制器仍使用旧配方的严重问题。最终通过实现强制的版本校验机制解决了这个问题:
public class RecipeExecutor
{
public void Execute(Recipe recipe)
{
var latest = RecipeManager.Instance.GetRecipe(recipe.Code);
if (latest.Version != recipe.Version)
throw new StaleRecipeException(recipe.Code);
// 执行逻辑...
}
}
这套配方管理系统架构已经在多个行业项目中得到验证,从食品加工到化工生产,稳定的核心架构配合灵活的扩展点设计,能够适应各种业务场景的演进需求。当需要新增配方类型时,只需扩展工厂类;当需要添加新的管理功能时,通过接口新增方法即可,充分体现了开闭原则的价值。
2万+



被折叠的 条评论
为什么被折叠?



