0

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?

5
  • Look at models for files and directories, that's the same. Directories (groups) can contain files (items) or other directories (groups). Commented Dec 3, 2020 at 9:58
  • Thanks for your comment, Rik D. I did consider file systems, though it's not exactly the same -- in file systems you could (in theory) keep nesting folders infinitely. In my model this is not supposed to be possible; you can group on 2 levels, that's it. I was trying to enforce this requirement in the model, so I don't have to hardcode it. Commented Dec 3, 2020 at 10:09
  • yeah, do you really have "supergroups" or can group just be made more generic. Commented Dec 3, 2020 at 10:10
  • Ewan -- Group and SuperGroup are different classes with different attributes and other associations. They share a couple of attributes, but that's it. Obviously I'm using a generic vocabulary instead of the actual, domain-specific class names. Commented Dec 3, 2020 at 10:17
  • @TheFox: That limitation could be enforced by simply not allowing a third level of groups (i.e. you can't add a child group to a group that already has a parent, and you can't assign a parent group to a group who already has a child group). The more important question here is if supergroups are actually different from groups or not. Commented Dec 3, 2020 at 12:17

3 Answers 3

1

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).
1

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
1

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.

8
  • You seem to assume OP is talking about domain objects, but I don't think you can particularly ignore that most persistence layers use some manner of classes to deserialize the database content into. There's nothing in OP's question that contradicts them trying to design these data classes as the proxy for their database table entries (i.e. entities). Furthermore, in any reasonable good practice design, those entities should very specifically not also carry the responsibility of the business/domain behaviors, which negates your point that the class structure hinges on the needed behavior. Commented Dec 4, 2020 at 14:28
  • @Flater I would not recommend the use of any "persistence layer" that would require creating faux objects, i.e. data structures. So my point stands, that all objects should be based on behavior. But even if the OP means that, those classes generally should mirror the database schema, shouldn't they? Is this a database schema question? Or is it a specific ORM question? It is tagged as object-oriented-design though. Maybe it's just wrong tags. Commented Dec 4, 2020 at 15:18
  • Regardless of whether the database is designed first or not, you need some kind of class to hold that data when it's been fetched from the database. Or are you suggesting we all go back to using DataTable for our database results? Secondly, if you argue that your domain entities must not have an intermediary data entity transformation, you therefore require your domain entities to conform to the database structure (since they then must be compatible), which outright violates the notion that your domain should not depend on its data store. Commented Dec 4, 2020 at 15:23
  • The application is very data-oriented, hence why I'm talking about database schemas. I haven't decided a technology for implementation, probably some web framework but I'd like a native mobile implementation as well.I'm essentially designing a PRM (personal relationship manager). Commented Dec 4, 2020 at 16:32
  • @Flater In an object-oriented system there is no such thing as independent "data". There is no Person object that just holds all attributes of a "person". That makes no real sense. If you fetch any data from the database it is in the context of a behavior with a specific business-related goal. So yes, you may create some objects with that data, but the data would never be "available" anywhere as such. All the problems you mention don't exist if you just don't think in the context of ORMs. Commented Dec 4, 2020 at 16:34

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.