One of the great features of Object Pascal is the inclusion of sets in the language.
Other languages have added libraries in support of sets, but Pascal remains unique in having always supported them.
Sets are often useful in combination with enumerations. Used together, they provide a very readable approach to handling things such as user options. Consider, for example, the management of a few dozen options in support of a report. Or better, a collection of reports, each of which supports some of the options defined in your enumeration. And for each report, the supported options varies.
In my work, I have found it useful to approach the challenges with a single large enumeration, and let the options forms in the print wizard deliver the set needed for the particular collection of options.
Readability is enhanced, I find, if you make use of some simple methods, as shown below.
/// Returns true when OptSet matches all options in FOpts
function MyOptions.AllOf(OptSet: TEBPrintOptions): Boolean;
begin
Result := OptSet * FOpts = OptSet;
end;
/
// Returns true if any of the options specified are present in FOpts, the user selections
function MyOptions.AnyOf(OptSet: TEBPrintOptions): Boolean;
begin
Result := OptSet * FOpts <> [];
end;
/// Returns true when none of the options in OptSet are present in FOpts
function MyOptions.NoneOf(OptSet: TEBPrintOptions): Boolean;
begin
Result := OptSet * FOpts = [];
end;
I like to use these simple routines because I don’t have to decipher the intent of the set operation while reading the code, but moreover, it is generally more concise. And although I do make extensive use of sets in handling options for reports and exports, my colleagues on the project rarely do, and this is much easier for them to read, when they must.
Keep in mind that a set in Delphi is stored in an array of bytes. As such, you may wish to convert the set to something more easily handled for persistence. Years ago, I added support to pack the set into an Int64. This is easily stored in a database, for example, and is also handy when you must pass the options to a COM server, for example. Conversion is simple:
function SetToUInt64(const aSet; const NumBytes: integer = 8): UInt64;
begin
Result := 0;
Move(aSet, Result, NumBytes);
end;
procedure UInt64ToSet(const Value: UInt64; var aSet; const NumBytes: Integer = 8);
begin
Move(Value, aSet, NumBytes);
end;
Should you find this useful, I offer this full, but small, implementation of a class. This had to be useful without using generics, but if you do not suffer that limitation, you can certainly use them.
unit uOptSets;
interface
type
TOptions = (oAllCategories, oSalesCategories, oRevenueCategories,
oOrderDetails, oOrderTotals, oShippedDetails, oShippedTotals,
oRevenueDetails, oRevenueTotals);
TOptionSet = set of TOptions;
TOptionClass = class
private
FOptionSet: TOptionSet;
public
function AllOf(OptSet: TOptionSet): Boolean;
function AnyOf(OptSet: TOptionSet): Boolean;
function NoneOf(OptSet: TOptionSet): Boolean;
property OptionSet: TOptionSet read FOptionSet write FOptionSet;
end;
var
OptionClass: TOptionClass;
implementation
{ TPrintOptionClass }
function TOptionClass.AllOf(OptSet: TOptionSet): Boolean;
begin
Result := OptSet * FOptionSet = OptSet;
end;
function TOptionClass.AnyOf(OptSet: TOptionSet): Boolean;
begin
Result := OptSet * FOptionSet <> [];
end;
function TOptionClass.NoneOf(OptSet: TOptionSet): Boolean;
begin
Result := OptSet * FOptionSet = [];
end;
end.
Code language: HTML, XML (xml)
Another possibility, and one I expect to use in a current project, is to persist by storing the names of the enumeration members. Unless the number of options you must support is huge, the impact of storing a CSV string of the selected members is modest. But the advantage in readability — even in the database — is large.
Your own requirements may inspire you in a different direction, but keep in mind the benefit of making things easily readable.