Multi-value Flag
Pattern (a.k.a. specialized enumerations)
In software development, a flag is a mark placed on an
object instance or data record in order to signal a particular status or
condition or to keep a record on whether a particular event has occurred
concerning the specified data. Mostly flags are binary, meaning they represent
a Boolean value; they are either true or false. We see many binary flags
represented as Boolean typed properties or bit typed data fields and they
include cases like marking an object as “Active”, “Obsolete”, “Recorded”, among
others.
Sometimes it is necessary for a flag to be able to get more
than the two binary values “true” and “false”. In these cases, enumerations
come handy as they provide a very simple way to declare, use and store several
values without having to deal with error prone value types while at the same
time being able to store easy to convert values in the data repository. So by
using enumerations you can relate “Inserted” with 0, “Reviewed” with 1,
“Approved” with 2, “Published” with 3, and finally “Edited” with 4. With this
relationships you are able to flag your object as “inserted” in the system,
later after someone has gone through the contents making sure everything is OK
you can change its value to “Reviewed”; after the revision is done content
needs to be “approved” by a third actor and finally all approved contents are
“published” to the UI. Days later the content is “edited” again and it needs to
go through the process again.
There are however, very specific cases when representing
flag values with integers do not meet the requirements, as maybe you need the
raw data returned by a query to the database to be understood at first sight or
to be provided or used by third parties while at the same time you have to
consider storage limitations and using lengthy flag values might be
unreasonable. The easier way to solve this problem would be to use a single
character for the flag values, as they are easy to remember and take only a
byte for each record. Using this technique we can represent the values
mentioned before by storing “I” for “Inserted”, “R” for “Reviewed”, “A” for
“Approved”, “P” for “Published” and “E” for “Edited”. But as we cannot assign
String values to enumerations we lose the convenience of strong-typing and
might get trapped in error prone practices like leaving the value that is
stored open to the developers, who might unknowingly type wrong values in
certain portions of code. In order to be able to use this kind of values in the
data repository we need to create a way to solve the following problems:
1. Being
able to use strong typed values during development.
2. Being
able to avoid hard-to-maintain conversion techniques between strong typed
values and value types.
3. Being
able to still pass value types to the Data Abstraction Layer without having to
resort to case-by-case conversion techniques.
4. Ensuring
developers won’t be able to mistakenly pass inexistent values to the flag.
5. Begin
able to store non-numeric values in the database.
In order to achieve this behavior we need to “emulate” the
behavior of an enumeration, making sure we can associate non-numeric values to
the flag.
Let’s start by declaring the type we will use for our
specialized enumeration:
public sealed class ProductStatus {
#region Properties
/// <summary>
/// Value that will be represent this status in the data
repository.
/// </summary>
public String StoringValue {
get { return storingValue; }
}
private String storingValue;
/// <summary>
/// Value that will represent this status in the UI.
/// </summary>
public String DisplayValue {
get { return displayValue; }
}
private String displayValue;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the ProductStatus flag.
/// </summary>
/// <param
name="storingValue">Value that
will represent this status in the data repository.</param>
/// <param
name="displayValue">Value that
will represent this status in the UI.</param>
/// <remarks>
/// The constructor of the class is marked as private in order
to prevent external users from
/// being able to create new instances of the ProductStatus.
/// The multi-value flag pattern represents an extension of
the singleton pattern in which
/// instead of just one, a limited series of instances of the
class are allowed to exist.
/// </remarks>
private
ProductStatus(String storingValue, String displayValue) {
this.storingValue
= storingValue;
this.displayValue
= displayValue;
}
#endregion
}
The class is marked as “sealed” as the first barrier for preventing
developers from being able to assign incorrect values to the flag. If it is
possible to inherit from our class it would be possible to generate more
instances of the ProductStatus type leading to an incorrect assignment. The
class also declares a unique private constructor and two properties. The properties
will store the values that will be used to represent the status in the data
repository and the user interface. The constructor is marked as private in
order to control how many instances of the class are created at any given time.
Now that we have the reference type that will represent our
flag, we need to create its instances. In order to do that we will add the
following static properties to the class:
#region Static Properties
/// <summary>
/// Represents the pending status of a product.
/// </summary>
public static readonly ProductStatus Pending = new
ProductStatus("P",
"Pending");
/// <summary>
/// Represents the approved status of a product.
/// </summary>
public static readonly ProductStatus Approved = new
ProductStatus("A",
"Approved");
/// <summary>
/// Represents the published status of a product.
/// </summary>
public static readonly ProductStatus Published = new
ProductStatus("B",
"Published");
#endregion
Now we can easily reference each instance the same way we do
with enumerations, using the “Type.Instance” formula:
product.Status = ProductStatus.Approved;
In order to make sure the properties declared with this type
on the business object are going to be compatible with the model we’re using
where the BAL and the DAL are loosely-coupled, it is necessary to ensure our
instance will implicitly be converted to String, just like enumerations are
seamlessly converted to Int32 values. We achieve this by declaring implicit
operators on the class:
#region Operators
/// <summary>
/// Implicitly converts the status value into the storing type
in order to
/// achieve transparent communication between loosely coupled
layers.
/// </summary>
/// <param
name="value">Status value to be
converted.</param>
/// <returns>Resulting status value.</returns>
public static implicit operator String(ProductStatus value) {
return
value.StoringValue;
}
/// <summary>
/// Implicitly converts storing type (String) values into
(allowed) product
/// status values so that they can be retrieved from the
database without
/// the need for explicit conversion. This eliminates the need
for the data
/// abstraction layer to know about the status pattern
implementation.
/// </summary>
/// <param
name="value">String value to
convert.</param>
/// <returns>The status value that corresponds to this string.</returns>
public static implicit operator ProductStatus(String value) {
return ProductStatus.GetProductStatus(value);
}
#endregion
The implicit operator uses the following methods to obtain
its value:
/// <summary>
/// Gets the collection of allowed values of this class.
/// </summary>
/// <returns></returns>
public static IEnumerable<ProductStatus> GetAllowedValues() {
yield return ProductStatus.Pending;
yield return ProductStatus.Approved;
yield return ProductStatus.Published;
}
/// <summary>
/// Searches inside the list of allowed values for one where
/// the storing value equals the proportioned string. If the
value
/// is not found null is returned.
/// </summary>
/// <param
name="value">String to look for
in the list of allowed values.</param>
/// <returns>The ProductStatus value corresponding to the specified
string or null if the value is not found.</returns>
public static ProductStatus
GetProductStatus(String storingValue) {
foreach
(ProductStatus status in ProductStatus.GetAllowedValues())
{
if
(status.StoringValue == storingValue) {
return
status;
}
}
return null;
}
Finally in order to complete the implementation we add
utility methods and override the ToString() method of the Object class to make
sure the correct values are printed on the UI without having to invest any additional
code in the presentation layer:
/// <summary>
/// Gets the storing value that corresponds to the specified
display value.
/// </summary>
/// <param
name="value">Display value to
find.</param>
/// <returns>A string that represents the storing value corresponding to
the specified display value or null.</returns>
public static String
GetStoringValue(String displayValue) {
foreach
(ProductStatus status in ProductStatus.GetAllowedValues())
{
if
(status.DisplayValue == displayValue) {
return
status.StoringValue;
}
}
return null;
}
/// <summary>
/// Gets the display value that corresponds to the specified
storing value.
/// </summary>
/// <param
name="value">Storing value to
find.</param>
/// <returns>A string that represents the display value corresponding to
the specified storing value or null.</returns>
public static String
GetDisplayValue(String storingValue) {
foreach
(ProductStatus status in ProductStatus.GetAllowedValues())
{
if
(status.StoringValue == storingValue) {
return
status.DisplayValue;
}
}
return null;
}
/// <summary>
/// Returns the UI-compatible representation of the status.
/// </summary>
/// <returns></returns>
public override string
ToString() {
return this.DisplayValue;
}
If in the future it is necessary to add new status values to
the class it would be necessary to add two lines of code to the implementation:
public static readonly ProductStatus NewStatus = new
ProductStatus("N",
"NewStatus");
Declaring the static property.
public static IEnumerable<ProductStatus> GetAllowedValues() {
yield return ProductStatus.Pending;
yield return ProductStatus.Approved;
yield return ProductStatus.Published;
yield return ProductStatus.NewStatus;
}
Adding the additional return value to the GetAllowedValues()
method.
You can download the complete implementation and a very
simple use case in a WinForms UI here.