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:
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.