how to design a custom product builder

跟風遠走 提交于 2019-12-13 17:25:04

问题


I am developing an application for generating estimates on products such as cars. So, when a car make an model is selected, I need to present various options to the user (options may be in different groups like Wheels, Seating Upholstery, Trunk Accessories)

Depending upon the group, the customer may pick one or more options in that group; if a certain option is selected - some other options may get disabled; not all options apply to every make an model So, there are several rules to be defined for different groups to indicate what combination is allowed and what is not allowed?

How should I go about designing the database for this and is there a pattern that I can leverage as I develop this application?


回答1:


I solved a similar requirement with the following structure, rewritten in your terms above:

  1. Parts
  2. Groups
  3. Car

With the following notes:

  1. Parts are standalone in their own right, each with a part number.
  2. A car template is standalone in its own right.
  3. Parts can be added to a option group, and a number of options groups belongs to a car.
  4. An option group cannot exist without a car.
  5. A group can depend on another group
  6. I need to protect against circular references

I started out playing with my model by writing the test case before i wrote the class code (Test Driven Development), which gave me code (in C#) as:

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);

I add the dependency option on the groups, rather than on the Sku, since a part may exist across multiple cars but may have different dependencies for each specific car.

I have to put everything through the root car object, which will check and enforce all my business rules (such as checking for and protecting against circular references).

Should all access via the car object feel clunky, you could always have the car.AddGroup function return a group to make the code make more sense to read:

var engines = car.AddGroup("Engines");
engines.AddSkuToGroup(diselEngine);
engines.AddSkuToGroup(petrolEngine2);
engines.AddSkuToGroup(petrolEngine25);

But do not forget the business rules can only be enforced by the car, since the car has visibility of all the components. So we always chain up via the root:

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;
    }
}

For the actual database storage i would worry about persistence last, it could be a text file, MSSQL database, MySQL database, MongoDB, there are so many options.

I find it always useful to concentrate on how i want to use the code in my application, and not the specifics of how the database needs to be used, as the storage can abstracted via a repository interface that returns a class (a plain old POCO class, but in this case we have started to flesh out with business logic to protect against invalid states).

For the front end, you may want to push this all down via JSON to something like angular or knockout that can render the options available dynamically, and show or hide different elements depending on the dependencies between groups.

Working fronted example

I am not sure what front end binding you are using (or if you will only be using razor, in that case you will need to store state on the server and refresh each selection), but I have provided an example using Knockoutjs here: http://jsfiddle.net/g18c/5jt9bwsv/1/ with working dependencies and dynamic javascript object builder.

  1. Loops through provided JSON products by group
  2. Creates calculated fields that change depending on a target dependency
  3. Binds to a view via knockout

The selected SKUs could then simply be passed up to the server, any business rules can also be implemented in the front end javascript.

Of course anything data that is sent from the client to the server would need to be validated by building up the product graph on the server and checking that the provided SKUs are valid (i.e. you wouldn't want to allow a Diesel Turbo to be selected for a Petrol Engine).



来源:https://stackoverflow.com/questions/26524895/how-to-design-a-custom-product-builder

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!