Modeling flags/labels in code
make FlagObject
an immutable type (all fields "readonly). That lets you pass objects of it around like strings or 'Flags' with no need to copy them explicitly, without the risk of introducing unwanted side effects.
choose the enums in FlagObject
as orthogonal as possible. There should not be a ContainerType
on one hand (which can already indicates whether something is a container or not) , and an enum ObjectType
with a value Container
as well, that's clearly redundant.
Instead, you can implement derived properties like a boolean property
IsContainer => ContainerType != ContainerType.None
Moreover IsEdible
should probably be just a boolean property, not mutual-exclusive with IsContainer
- technically(why should every container not be edible?). Technically you can also use an enum Editbility { None = 0, Edible = 1}
if you prefer this for reasons of unitformity.
provide explicit constructors which check any extra constraints of mutual-exclusive enum values (and throw exceptions in case those constraints are violated, for example, when someone tries to construct an edible metal object).
provide certain default values in the constructor parameters, especially when new flags are introduced at a later point in time. That saves you from changing existing code already relying on FlagObject
s.
There are case where #2 your Flags
enum has some advantages. These are cases where you need to optimize heavily for memory, performance, or where you are abound by certain requirements related to external APIs or persistence. But if this is not the case, I would recommend to go with a FlagObject
first. And if you really come to the point wher you need some optimization, you have always the option of keeping the public API of a FlagObject
unchanged and implement it as a bitset like Flags
internally at a later point in time.
Modeling flags/labels by a user of the system
In a comment, you mentioned that you see some requirement to create the flags/labels and their relationships as dynamic data in your system, so they can be modified without changing the actual program. This makes most sense when the person who maintains the flags/labels isn't the developer, but a user, or maybe some power user or administrator.I assume there is something like a list of items which are tagged accordingly.
For this, you first need to model the possible label categories themselves in code, each one with a list of labels (within the category):
class LabelCategory
{
public readonly string Name;
public readonly string[] Labels;
}
Then you need a UI to maintain a List<LabelCategory> availableCategories
and persist it somewhere.
Next you will have to categorize your items. Each item can have a list of lables from different categories:
class Tag
{
public LabelCategory Category;
private int labelIndex;
public string Label => Category.Labels[labelIndex];
public Tag(LabelCategory category, int labelIndex)
{
if(labelIndex<0 || labelIndex >= category.Length)
throw ArgumentException("...");
this.Category=category;
this.labelIndex=labelIndex;
}
}
(This is just a rough scetch, please read it as pseudocode, just as the following code, a real implementation should be more sophisticated and may implement anything in an immutable fashion).
Next, I would introduce a class Tagsets
holding a list of tags from different categories:
class Tagset
{
public List<Tag> Tags;
// have some code making sure all tags belong to different categories
}
Then, each item gets a tagset:
class Item
{
public Tagset Tagset;
// ... specific item attributes
}
Let me finally come to the question of how to describe the relationships / constraints between the different tags / labels of an item, and how to validate these constraints.
In your example, you mention just two types of rules:
exclusion rules: certain combinations of tag values are not allowed (like "Metal" and "Edible")
inclusion rules: a certain tag value in one category implies the availability of another tag value in another category (like "Fruit" implies "Edible")
Both kind of rules can be described by lists of tagsets or list of tag pairs:
// each entry here describes a forbidden combination
List<Tagset> exclusionRules;
// eache entry says "Item1 implies Item2 to be in the same tagset"
List<(Tag,Tag)> inclusionRules;
Now it should be easily possible to create a UI for letting a user manage these exclusionRules
and inclusionRules
. In case you need other types of rules, please feel free to extend this model.
The validation of these rules then has to happen when the tagsets for items are created and called in that context. This is left as an exercise to the reader;-) Hope this helps.