Over the last couple of years, I have been making more use of nested types to organise how my code (behaviour) is organised in an application.  If some application behaviour (encapsulated in class A) is part of a broader behaviour (encapsulated in class B), then I will at least consider nesting class A within class B.

For example, if I were writing a (very simple) word processor and had a class for Document and a class for Paragraph, then I could declare these classes as:

class = TDocument
public
  type
    class = TParagraph
      ...
    end;
  ...
end;

Organising classes like this makes it easier to understand relationships between related behaviours in a program. It also makes it somewhat easier to find where particular behaviours are declared in the program as there is more organisation to classes.

The obvious downside is that class/type declarations are larger and harder to read.  With practice, I have gotten better at reading types with nested types but you can only go so far.

One area in which nested types works well, is in the declaration of enumerated types.  Often you want some behaviour associated with enumerated types.  For example, you may want to associate display names with them or what string value to use when writing out to XML.  Consider this example of an enumerated type which identifies the importance of a document:

TDocumentImportance = (diLow, diMedium, diHigh);

Nowadays, I would probably declare such an enumeration with the following type:

TDocumentImportance = record
public
  type
    TId = (diLow, diMedium, diHigh);
private
  type
    TInfo = record
      Id: TId;
      Name: string;
      XmlValue: string;
    end;
    TInfos = array[TId] of TInfo;
  const
    Infos: TInfos =
    (
      (Id: diLow; Name: 'Low'; XmlValue: 'Low'),
      (Id: diMedium; Name: 'Medium'; XmlValue: 'Medium'),
      (Id: diHigh; Name: 'High'; XmlValue: 'High')
    );
  class constructor Create;
public
  class function IdToName(Value: TId): string; static;
  class function IdToXmlValue(Value: TId): string; static;
  class function TryXmlValueToId(const XmlValue: string; out Id: TId): Boolean; static;
end;

Whenever I now want to define one of these enumerations, I use the type:

TDocumentImportance.TId;

The functions (behaviour) associated with Document Importance are now part of the TDocumentImportance record instead of being global functions. (I normally also include a class constructor to check that the order of the Info records matches the order of the TId enumeration to allow easy IdToXXX conversions.)

While declaring enumerated types this way may be a bit more work, I find its benefits in improving my “mental map” of the source code, justify the extra effort.

5 Responses

  1. I use this extensively for test cases, to define input and expected output. Unfortunately, the IDE seems to get confused with a lot of local or nested type declarations and const definitions. At some point it just keeps complaining about undefined identifiers and such… and fails to give code completion support. 🙁 However, everything compiles nicely…

  2. Can you please show a snippet of code on how to use it? I’ve tried but could not grasp how to do 🙁
    thank you, fabio