I rather avoid things like
ViewStatus viewStatus;
since it introduces a new class without giving you anything in return when compared to
boolean isViewed;
But that's a matter of personal taste.
By setting the reference to null
you can have a third value, which is not really good, since you can get an unexpected NPE
somewhere; null
is just no "first class value".
EnumSet pretty much exists specifically for this purpose, but... it seems like just two much overhead. Especially when reading the values in the part of the code that builds the HQL.
The overhead of an EnumSet
is reasonable, it caches the class and an array of all values, and uses a long
for storing the data (as long as the enum
is small enought). Its equals
method is quite fast, but obviously slower than reference comparison. You mentioned using HQL, so most probably the runtime will be dominated by the database access, so IMHO you're needless and premature optimization.
The mutability of the EnumSet
may be a problem sometimes, but there's ImmutableEnumSet in guava.
I'll go for an EnumSet because I want to introduce methods on that enum that wouldn't make sense to be called on ALL. For example ViewStatus.isViewed().
I don't think you stated all your alternatives right. You can have both
enum ViewStatus {VIEWED, UNVIEWED}
and
enum ViewStatuses {VIEWED, UNVIEWED, ALL}
where the second is an replacement for the EnumSet
. That said, I agree with your decision.
I agree with others that this seems overengineered. Usually, you want either a simple enum or a complex hierarchy of classes, it's not a good idea to combine the two.
But if you really want to do this (in C#), I think it's useful to recap what exactly do you want:
- Separate types for the hierarchy
Kingdom
, Phylum
, etc., which do not form inheritance hierarchy (otherwise, Phylum
could be assigned to Kingdom
). Though they could inherit from a common base class.
- Each expression like
Animalia.Chordata.Aves
has to be assignable to a variable, which means we have to work with instances, not nested static types. This is especially problematic for the root type, because there are no global variables in C#. You could solve that by using a singleton. Also, I think there should be only one root, so the code above would become something like Organisms.Instance.Animalia.Chordata.Aves
.
- Each member has to be a different type, so that
Animalia.Chordata
compiled, but Plantae.Chordata
didn't.
- Each member needs to somehow know all its children, for the
IsMember()
method to work.
The way I would implement these requirements is to start with a class like EnumSet<TChild>
(though the name could be better), where TChild
is the type of the children of this level in hierarchy. This class would also contain a collection of all its children (see later about filling it). We also need another type to represent leaf level of the hierarchy: non-generic EnumSet
:
abstract class EnumSet
{}
abstract class EnumSet<TChild> : EnumSet where TChild : EnumSet
{
protected IEnumerable<TChild> Children { get; private set; }
public bool Contains(TChild child)
{
return Children.Contains(child);
}
}
Now we need to create a class for each level in the hierarchy:
abstract class Root : EnumSet<Kingdom>
{}
abstract class Kingdom : EnumSet<Phylum>
{}
abstract class Phylum : EnumSet
{}
And finally some concrete classes:
class Organisms : Root
{
public static readonly Organisms Instance = new Organisms();
private Organisms()
{}
public readonly Animalia Animalia = new Animalia();
public readonly Plantae Plantae = new Plantae();
}
class Plantae : Kingdom
{
public readonly Anthophyta Anthophyta = new Anthophyta();
}
class Anthophyta : Phylum
{}
class Animalia : Kingdom
{
public readonly Chordata Chordata = new Chordata();
}
class Chordata : Phylum
{}
Notice that children are always fields of the parent class. What this means is that to fill the Children
collection, we can use reflection:
public EnumSet()
{
Children = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
.Select(f => f.GetValue(this))
.Cast<TChild>()
.ToArray();
}
One problem with this approach is that Contains()
always works only one level down. So, you can do Organisms.Instance.Contains(animalia)
, but not .Contains(chordata)
. You can do that by adding overloads of Contains()
to the specific hierarchy classes, e.g.:
abstract class Root : EnumSet<Kingdom>
{
public bool Contains(Phylum phylum)
{
return Children.Any(c => c.Contains(phylum));
}
}
But this would be a lot of work for deep hierarchies.
After all of this, you end up with quite a lot of repetitive code. One way to fix that would be to have a text file that describes the hierarchy and use a T4 template to generate all the classes based on that.
Best Answer
Perhaps due to my background in other languages where the typing of
enum
is more strict (*), I tend to think that if you want a type able to contain a set of constants, use anenum
, if you want a bunch of constants, use a bunch of constants and don't introduce a type where there is no need of one.There have been considerations which made the choice of an
enum
better at the time, but the only one I can think which survived the normalization of C++ 98 is compatibility with C and previous implementations of C++ (and some have kept as their style what was a necessity when they established it).What has survived are the considerations which made the use of an additional type worrisome. The fact that the constants are of another type which is normally not used can interfere with overload resolution and type deduction in templates. Rules are such that it does usually what you want -- perhaps at the cost of an additional instantiation for the spurious
enum
type -- but there are time when it isn't. For instancereturn a
MyClass<Size>
formake_MyClass(PAGE_SIZE)
where you probably expect aMyClass<int>
if the reason for introducing the enum wasn't to have a type but introduce some constants.(*) note that such stricter typing of
enum
was deemed sufficiently desirable that a variation is now provided asenum class
since C++11.