ColdFrame: Classes

Motivation

A class typically is an abstraction of something in the domain of interest. It represents the common properties and behaviour shared by all instances of the class.

Not all classes represent concrete concepts (such as Cat or Book). It's common for classes to represent incidents (Button Press), relationships (Book Borrowing) or roles (Hired Van).

See also:

Modelling

A class is modelled, of course, by a UML class.

In ColdFrame, not all UML "class" symbols represent what we're discussing here:

Given that, classes come in three flavours: normal, singleton and utility.

Utility classes have no instances (you could think of them as representing a library of functions).

A singleton class (marked «singleton») always has precisely one instance; you don't need to create it, and you can't delete it.

Normal classes start off with no instances; the instances have to be created, and may be deleted.

Instances of a normal class must be uniquely identifiable by some attribute or combination of attributes. The identifying attributes, like any other attributes, ought to be of non-composite types.

For example, a Book Edition is uniquely identified by its ISBN. This is an example of a naming attribute: Leon Starr says

[A naming attribute exists] not so much to describe, but to identify, instances. [...] A naming attribute may or may not be sufficient to uniquely identify instances of a class. For example, the attribute Employee Name is a naming attribute, but it is possible to have two employees with the same name.

In another example, a Vehicle Excise Disk (the round paper certificate, to be displayed on the vehicle's windscreen, that (until October 2014) showed you'd paid the UK road tax for the year) might be identified by the combination of Vehicle Index Mark (the string of letters and digits on the numberplate) and Validity Start Date. Vehicle Index Mark and Validity Start Date then become "identifying attributes", whereas Date Of Issue and Period Of Validity are just common-or-garden attributes.

In ColdFrame, you must not specify identifiers for

All other classes must have identifiers. In this, ColdFrame doesn't follow Executable UML; it seems risky to apply too many defaults.

For the case where there's no suitable naming attribute, ColdFrame provides the attribute type Autonumber (rather like the Microsoft Access feature it's named after): each newly-created instance gets a new value.

Use of Stereotypes

Classes which represent user-defined types are marked with the stereotype «datatype». (The stereotypes «callback», «counterpart», «discriminated» and «protected» imply «type»).

Classes of which there is always precisely one instance are marked with the stereotype «singleton».

Public classes, which are normally the only classes visible from outside the Domain, are marked with the stereotype «public». They can have at most one instance (you don't need to say «singleton»). Public classes can't take part in associations.

In ColdFrame, all parents in inheritance relationships are abstract and may be marked as such using the isAbstract checkbox in the modifiers section of the class's ArgoUML Properties tab. This has no effect on code generation at present.

Active classes (where each instance is to be associated with a run-time thread or task) may be marked with the stereotype «active» or, if preferred, by setting the isActive checkbox in the modifiers section of the class's ArgoUML Properties tab (which is less immediately visible, and doesn't allow the associated tagged values to be used).

The stereotype «active» has associated tagged values

priority = priority
allows you to specify the priority of each instance of an active class, relative to the system's default priority (larger numbers mean higher priority).
stack = stack-size
allows you to specify a non-default stack size for each instance of an active class.

By default, a class's abbreviation (used in the translation of associations and generalizations) is composed of the initial letters of the words that make up the full class name or, if the name is a single word, the name prefixed with A_ or An_ as appropriate. This rule wouldn't work for, for example, New Threat and Non Threat. If this happens, or if the abbreviation turns out to be a reserved word, use «abbreviation», with its associated tagged value abbreviation = abbrev, to specify an alternative.

Translation

Ada library structure

All the classes in a domain are translated as first-level children of the domain package:

private package Domain.Class is
   ...
end Domain.Class;

(private applies to all the classes bar those marked «public»).

If the class is «public» or a «singleton», that's it. For normal classes, the following additional library units are generated:

Domain.Class.All_Instances
A function which returns a Vector containing the Handles of all the current instances of the class.
with Domain.Class.Vectors;
function Domain.Class.All_Instances
  return Domain.Class.Vectors.Vector;
pragma Elaborate_Body (Domain.Class.All_Instances);
Domain.Class.Selection_Function
A generic function which is instantiated with a Pass function that takes a Handle and returns True if the instance is required. It takes no parameters and returns a Vector containing the Handles of all the current instances of the class that pass the Pass function.
with Domain.Class.Vectors;
generic
   with function Pass (This : Handle) return Boolean is <>;
function Domain.Class.Selection_Function
  return Domain.Class.Vectors.Vector;
pragma Elaborate_Body (Domain.Class.Selection_Function);
Domain.Class.Filter_Function
A generic function which is instantiated with a Pass function that takes a Handle and returns True if the instance is required. It takes a Vector parameter and returns another Vector containing the Handles of all the instances in the input Vector that pass the Pass function.
with Domain.Class.Vectors;
generic
   with function Pass (This : Handle) return Boolean is <>;
function Domain.Class.Filter_Function
  (The_Vector : Domain.Class.Vectors.Vector)
  return Domain.Class.Vectors.Vector;
pragma Elaborate_Body (Domain.Class.Filter_Function);
Domain.Class.Iterate
A simple generic closed iterator over Vectors; to be instantiated with the procedure to be called to process each Handle.
with Domain.Class.Vectors;
generic
   with procedure Process (H : Handle);
procedure Domain.Class.Iterate
  (Over : Domain.Class.Vectors.Vector);
pragma Elaborate_Body (Domain.Class.Iterate);

Package externals

A class's identifying attribute or attributes map to a record type named Identifier. Note that «public», «singleton» and «utility» classes don't have identifiers.

A class's instance data maps to a private record type named Instance, with an access type Handle. In the case of «public» classes, these are both declared in the private part (in other words, they're only visible to operations of the class itself). Both «public» and «singleton» classes have a private variable This of type Handle, which accesses the only instance of the class. See also under Attributes.

Normal classes have a Create operation, returning a Handle. If the class's only identifying attribute is of type Autonumber, Create takes no parameters; otherwise it takes a single parameter of type Identifier.

They also have a pair of Delete operations; one takes a parameter This of type Handle, the other takes a single parameter of type Identifier.

Non-«public», non-«utility» classes have a Find operation:

Non-«public», non-«utility» classes have a package Vectors, which is an instantiation of Ada.Containers.Vectors (or Bounded_Vectors, if the maximum number of instances is known) for Handles.

Package internals

In case operations may need them, ColdFrame automatically places withs in the body of a class package for all class packages where

more to come

Use

If you're implementing operations of a «singleton» or 0 .. 1 class, use This instead of Find; it'll be quicker.

The identifier is always a record type, even if there's only one attribute in it, so you'll need an aggregate; if there is only one attribute, you'll need to qualify the aggregate or use named association, so that Ada can tell it's actually an aggregate and not an expression: for Fruit, for instance,

   Create ((ID => Apple));