我用下面的结构解决了一个类似的要求,用你上面的术语重写:
- 零件
- 组
- 汽车
附注如下:
- 部件本身是独立的,每个部件都有一个部件号。
- 汽车模板本身就是独立的。
- 可以将零件添加到一个选项组中,并且多个选项组属于一辆车。
- 没有汽车就不能存在选项组。
- 一个组可以依赖另一个组
- 我需要防止循环引用
在编写类代码(测试驱动开发)之前,我通过编写测试用例开始使用我的模型,这给了我代码(在 C# 中):
var dieselEngine = new Sku("diesel 2_litre,",1000);
var petrolEngine2 = new Sku("petrol_2_litre",800);
var petrolEngine25 = new Sku("petrol_25_litre",900);
var petrolTurbo = new Sku("petrol_turbo",2000);
var dieselTurbo = new Sku("diesel_turbo",2000);
var car = new Car("myCar");
car.AddGroup("Engines");
car.AddSkuToGroup("Engines", diselEngine);
car.AddSkuToGroup("Engines", petrolEngine2);
car.AddSkuToGroup("Engines", petrolEngine25);
car.AddGroup("Turbos");
car.AddSkuToGroup("Turbos", petrolTurbo);
car.AddSkuToGroup("Turbos", dieselTurbo);
car.SetRequirement(diselEngine, dieselTurbo);
car.SetRequirement(petrolTurbo, petrolEngine2);
car.SetRequirement(petrolTurbo, petrolEngine25);
我在组而不是 Sku 上添加依赖项选项,因为一个部件可能存在于多辆汽车中,但对于每辆特定汽车可能具有不同的依赖关系。
我必须通过根汽车对象放置所有内容,这将检查并执行我的所有业务规则(例如检查和防止循环引用)。
如果通过汽车对象进行的所有访问都感觉很笨重,您总是可以让car.AddGroup 函数返回一个组,以使代码更易于阅读:
var engines = car.AddGroup("Engines");
engines.AddSkuToGroup(diselEngine);
engines.AddSkuToGroup(petrolEngine2);
engines.AddSkuToGroup(petrolEngine25);
但不要忘记,业务规则只能由汽车执行,因为汽车具有所有组件的可见性。所以我们总是通过根链接起来:
class ConfigurableProduct
{
List<Group> groups = new List<Group>();
Group NewGroup(string name)
{
var group = new Group(this, name);
this.groups.Add(group);
return group;
}
bool ContainsSku(string skuId)
{
foreach (var group in this.Groups)
{
if (group.ContainsSku(skuId))
return true;
}
return false;
}
}
class Group
{
Group(ConfigurableProduct parent, string name)
{
this.parent = parent;
this.name = name;
}
string name;
List<string> skuIds = new List<string>();
ConfigurableProduct parent;
void AddSkuToGroup(string skuId)
{
// enforce invariants via parent, call functions as reuqired
if (this.parent.containsSku(skuId))
throw new Exception("SKU already exists in this configurable template, cannot exist twice");
// do other things, like check circular references etc, all via this.parent
}
bool ContainsSku(string toFind)
{
foreach (var skuId in this.skuIds)
{
if (skuId == toFind)
return true;
}
return false;
}
}
对于实际的数据库存储,我最后会担心持久性,它可以是文本文件、MSSQL 数据库、MySQL 数据库、MongoDB,有很多选择。
我发现专注于我想如何在我的应用程序中使用代码总是有用的,而不是需要如何使用数据库的细节,因为可以通过返回类的存储库接口抽象存储(a普通的旧 POCO 类,但在这种情况下,我们已经开始充实业务逻辑以防止无效状态)。
对于前端,您可能希望通过 JSON 将这一切下推至诸如 angular 或 knockout 之类的东西,它们可以动态呈现可用选项,并根据组之间的依赖关系显示或隐藏不同的元素。
前端工作示例
我不确定您使用的是什么前端绑定(或者如果您只使用剃须刀,在这种情况下,您需要在服务器上存储状态并刷新每个选择),但我提供了一个使用 Knockoutjs 的示例这里:http://jsfiddle.net/g18c/5jt9bwsv/1/ 具有工作依赖项和动态 javascript 对象构建器。
- 按组循环提供的 JSON 产品
- 创建根据目标依赖关系变化的计算字段
- 通过敲除绑定到视图
然后可以简单地将选定的 SKU 传递给服务器,任何业务规则也可以在前端 javascript 中实现。
当然,从客户端发送到服务器的任何数据都需要通过在服务器上构建产品图并检查提供的 SKU 是否有效来进行验证(即,您不希望允许 Diesel Turbo被选为汽油发动机)。