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 FlagObject
s 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 FlagObject
s 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 aContainerType
on one hand (which can already indicate if something is a container) , and anObjectType
which allows a typeContainer
as well, that's clearly redundant.Instead, you implement derived properties like a boolean property
IsContainer => ContainerType != ContainerType.None
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
FlagObject
s.
That's IMHO a readable and clean solution, hence that is the way I would start with.
There are case where #2 your Flags
enum has some advantages. These are case 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.