Skip to main content
2 of 5
added 224 characters in body
Doc Brown
  • 214k
  • 34
  • 394
  • 603

It is not really clear from your contrived example whether your approach #1 means "an object with these flags, plus some extra, non-flag properties not mentioned", or if your ObjectWithFlags should be just a FlagObject - a type holding these flags exclusively, and the objects which are labelled get a member variable of type FlagObject indicating their "domain types". So let me assume it is the latter, and I will speak of FlagObjects to make this clear.

Let's look at what you wrote about your second solution:

A single variable to store all of the flags of an object - easy to pass to functions, copy etc.

Sorry, but that is true for a FlagObject as well. You can create single variables of type FlagObject and can do almost everything what you can do with the type Flags, just in a more readable way. Of course, to make FlagObject really as easy to handle as Flags, you should change the design a little bit (see below).

No need to add new fields when a new flag appears, just add it to the enum.

When you add new flags, you will have the need to change something in the type Flags, and an equivalent change to FlagObject will be required as well. With the right kind of design, you can prevent changes of existing code with FlagObjects just the way as with Flags.

So as you noted, I strongly prefer your approach #1, but as I said, I would change the design a little bit. I would

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

That's IMHO a readable and clean solution, hence that is the way I would start with. I leave it as an exercise to you write down a implementation of FlagObject with these recommendations in mind.

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.

Doc Brown
  • 214k
  • 34
  • 394
  • 603