Skip to main content
added 1 character in body
Source Link
Doc Brown
  • 214k
  • 34
  • 394
  • 603

There are casecases 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.

Next you will have to categorize your items. Each item can have a list of lables fromlabels of different categories. I call this a Tag, to make the difference clear:

(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 anythingthese classes in an immutable fashion).

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.

Next you will have to categorize your items. Each item can have a list of lables from different categories:

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

There are cases 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.

Next you will have to categorize your items. Each item can have a list of labels of different categories. I call this a Tag, to make the difference clear:

(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 these classes in an immutable fashion).

added 3332 characters in body
Source Link
Doc Brown
  • 214k
  • 34
  • 394
  • 603

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 FlagObjects.

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.

  • 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 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 FlagObjects.

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 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 (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 FlagObjects.

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.

added 235 characters in body
Source Link
Doc Brown
  • 214k
  • 34
  • 394
  • 603
  • 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 indicate ifindicates whether something is a container or not) , and an enum ObjectType which allowswith a typevalue 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 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 execeptionsexceptions in case those constraints are violated, for example, when someone tries to construct an edible metal object).

  • provide certain default values in the constructorsconstructor parameters, especially when new flags are introduced at a later point in time. That saves you from changing existing code already relying on FlagObjects.

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 needcome to optimize for space later,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.

  • make FlagObject an immutable type (all fields "readonly). That lets you pass objects of it around like strings with no need to copy them, 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 indicate if something is a container) , and an ObjectType which allows a type Container as well, that's clearly redundant.

    Instead, you 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.

  • provide explicit constructors which check any extra constraints of mutual-exclusive enum values (and throw execeptions in case, for example, someone tries to construct an edible metal object).

  • provide certain default values in the constructors, especially when new flags are introduced at a later point in time. That saves you from changing existing code already relying on FlagObjects.

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 need to optimize for space later, you have always the option of keeping the public API of a FlagObject unchanged and implement it as a bitset internally at a later point in time.

  • 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 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 FlagObjects.

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.

added 224 characters in body
Source Link
Doc Brown
  • 214k
  • 34
  • 394
  • 603
Loading
Source Link
Doc Brown
  • 214k
  • 34
  • 394
  • 603
Loading