Possibly my problem already has a solution, but I couldn't find it as I don't know the correct terminology.

Essentially I have a class called 'Item'. Usually this item is grouped into Groups (N:1 relationship), which are in turn grouped into SuperGroups. It's also possible for Items to be associated directly with their SuperGroup, i.e. without being in a Group. Here's a visualization:

A visualisation of the relationship between the classes

So an Item can have either a Group or SuperGroup as a parent, but not both. Note that Item3 has the SuperGroup A as direct parent, but other Items have a Group as parent.

How can I best solve this? My first thought was create some sort of interface IGroup, like below. This avoids hardcoding the business rule, but it might potentially add an extra table in my database schema (in case it's not possible to have Group and Supergroup in the same table).

Solution class diagram with interface.

How would you guys handle this situation?

有帮助吗?

解决方案

Whenever you deal with similarities (items can have different kinds of parents), you need to look for the abstractions.

The correct solution depends on a few considerations. One major question here is whether supergroups are actually different from groups, or whether you're just naming them "supergroups" because they contain other groups but they otherwise behave the same.

If groups and supergroups behave the same, then the solution is simple: a group can contain items and subgroups. The same is true of each subgroup.

public class Group
{
    public Item[] Items { get; set; }
    public Group[] Subgroups { get; set; }
}

You mentioned in the comments that there is a limitation on how many group levels you have, this is something you can enforce using business logic rather than letting this steer your class design.

If supergroups are different from group, the next question is whether a supergroup can be derived from a group. If that is possible, then you can solve this using simple inheritance:

public class Group
{
    public Item[] Items { get; set; }
}

public class SuperGroup : Group
{
    public Group[] Groups { get; set; }
}

Because of the inheritance, any SuperGroup will also have its own Items property.

If they cannot derive from one another, then we are left with only one similarity between the two: they can both contain items. Since that is still shared logic, it can be abstracted into its own class/interface.

public interface IItemContainer
{
    Item[] Items { get; set; }
}

public class Group : IItemContainer
{
    public Item[] Items { get; set; }
}

public class SuperGroup : IItemContainer
{
    public Item[] Items { get; set; }
}

In all of the above cases, the "item containing" behavior of both groups and supergroups can now be reusably developed.


My first thought was create some sort of interface IGroup, like below. This avoids hardcoding the business rule, but it might potentially add an extra table in my database schema (in case it's not possible to have Group and Supergroup in the same table)

First of all, if having that extra table ensures a better data format, then you shouldn't avoid the better data format just to save on one table. Your domain shouldn't be built based on your database format.

Secondly, you can't have a table for an interface. While you could serialize data into the table, you wouldn't be able to deserialize data from that table, since you need a concrete class to deserialize it to. And if you have that concrete class, then you might as well just use that concrete class to begin with.

Other than that, if you happen to be using Entity Framework, there are some options here on whether you use table-per-type, table-per-hierarchy, or table-per-concrete class. I very much prefer the latter in cases where it's applicable, but do note that EF Core does not yet support it as of right now.


Some notes:

  • I used arrays here, but any type of collection would work.
  • Whether you use interfaces or base classes is up to you. Interfaces are generally preferred but either would work.
  • Limitations on group recursion depth are better solved using business logic instead of trying to mold your classes. It's more maintenance-friendly and it keeps your classes easier to (reusably) handle.
  • I made them publically settable properties for the sake of example. Feel free to change their access modifiers as you see fit (e.g. readonly).

其他提示

My goto approach with random groups is to do them as separate lists of objects.

But if you want to enforce the system in your object model maybe you could simplify the rule by having invisible groups.

ie

  • all items are in a group.
  • all groups are in a super group
  • some groups are invisible (eg item 3)
  • OR if an item is the only item in its group it counts as being in the super group directly for whatever calculation

You haven't presented a problem yet for an object design, because you described no behavior. It is also confusing that you bring in some Database aspects, which should not matter at all for objects, unless you are misusing objects to hold data.

So for an object design, you'll have to tell us how those objects are actually used. And I don't mean "add or remove nodes from groups" or things like that, but really, what is it for. What logic would a user execute where this information is relevant.

There can not be an object design without answering these questions.

许可以下: CC-BY-SA归因
scroll top