The first thing you will want to do is create the classes that will implement your application. This chapter describes class creation protocol, including some special features that can apply to all instances of a class.
Subclass Creation
explains how to define new GemStone classes, class implementation formats and other ways classes can store data.
Creating Classes With Invariant Instances
describes how to make objects invariant.
Creating Classes with Special Cases of Persistence
explains how classes can be defined so that their instances or instance variables are not stored in the repository.
Almost every class in the GemStone system understands a message that causes it to create a subclass of itself.
Object subclass: 'Animal'
instVarNames: #('habitat' 'name' 'predator')
classVars: #('AllAnimals')
classInstVars: #('AllOfSpecies')
poolDictionaries: #()
inDictionary: UserGlobals
This subclass creation message establishes a name ('Animal') for the new class and provides for three named instance variables ('habitat', 'name', and 'predator'), a class variable ('AllAnimals'), and a class instance variable ('AllOfSpecies'). The new class in installed in the symbolDictionary UserGlobals of the user who executes this code. You may also include reference to poolDictionaries, if this is useful for your application. Pool dictionaries are included by value, not by name; in other words, you use the reference to the pool dictionary, not a String.
The String used for the new class’s name must follow the general rule for variable names — that is, it must begin with an alphabetic character and its length must not exceed 1024 characters.
There are a number of subclass creation methods. The first keyword (in the example above, subclass:) defines the implementation format — more on this in the next section. Subclass creation methods with additional keywords are provided to provide other information to use when creating the class.
Some GemStone server classes cannot be subclassed. This is an attribute of the class. Execute class subclassesDisallowed to determine if a specific class can be subclassed.
Objects typically encapsulate data and behavior. The behavior is defined as methods on a class and the data is stored in the object. The data may be stored in named instance variables, indexed instance variables (Collection elements), or by value in specialized internal structures.
The implementation format refers to how the basic structure of the objects are defined by the class, which is done when the class is created. Implementation may be inherited from the superclass, or by using specific subclass creation methods you can specify the implementation format of the class.
Many types of objects have named instance variables, but no indexable variables. Objects may have up to 255 named instance variables, which are referred to by name in the code for that class. This is the default format; subclass creation methods that begin with the subclass: keyword will create classes of this format, if another format is not inherited.
Indexable objects have a variable number of instance variables that are referenced by an Integer index. The number of an object’s indexed instance variables can increase dynamically at run time, up to 240-1 (about a trillion). There are two general cases of indexable objects:
Pointer-format
Pointer-format indexable objects allow the instance variables to refer to any other object. Pointer-format objects may also have up to 255 named instance variables.
Subclass creation methods that create indexed classes with pointer objects begin with the keyword indexableSubclass:.
Byte-format
This format is used for objects with indexed instance variables that are specialized for storing byte values, SmallIntegers in the range 0...255. Byte-format objects may not have named instance variables.
Subclass creation methods that create byte indexable classes begin with byteSubclass:.
You may not create byte-indexable subclasses of pointer-indexable classes, nor vice-versa, nor can you create indexable subclasses of NSCs.
These classes store data with neither names nor indexes. They are suited to applications in which access is by value, rather than by name or position. Classes with this format are subclasses of UnorderedCollection, and are the classes for which Indexes are implemented.
You cannot directly define classes with this format, although you can subclass from existing kernel classes. Subclasses of NSC classes may have named instance variables, but not indexed instance variables.
Instances of a few small, self-contained, kernel classes, including Character, SmallInteger, SmallDouble, Boolean, and UndefinedObject, are encoded entirely in the object identifier. Special objects do not use up an object ID (i.e., are not in the object table), do not take up separate space in the repository (beyond the original reference itself), and equal values always compare as identical.
You may not create your own specials nor may you subclass existing special classes.
The implementation formats defined in the last section define several types of instance variables. Class definitions also include the following variable types:
Class variables
A class variable is a variable whose name and value are shared by a class, all of its instances, its subclasses, and all of their instances. Both class and instance methods of the class and its subclasses can refer to the variable. You can think of these variables as falling somewhere between local and global in their scope.
Class instance variables
A class instance variable is a variable whose name and value are shared by a class, but not by its instances. Subclasses inherit the variable’s name but not its value. Only class methods of a class and its subclasses can refer to class instance variables. Class instance variables are useful when a class and its subclasses need to share the same structure, but not the same value, for a variable.
Pool variables
The pool variables are an Array of SymbolDictionary instances that are searched when attempting to bind a variable name during instance method compilation. Pool variables come after class variables and before globals in precedence. They are typically used when methods in a number of classes share values.
For example, one could define a SymbolDictionary with a key of #'CR' and a value of (Character codePoint: 13). If this SymbolDictionary were included in the class definition as a pool dictionary, then instance methods in the class could use CR as a way to reference the value and make the code more readable.
Global variables
Global variables are not tied to a class. They may be entries in a SymbolDictionary referenced in the UserProfile’s SymbolList.
In addition to the fixed instance variables, which are the same for every instance of that class, you may also add dynamic instance variables to most instances.
Dynamic instance variables are key/value pairs that are stored with the instance like other instance variables, but may be added to specific instances of a class an not to other instances, without changing the class definition.
You cannot add dynamic instance variables to invariant objects, nor to Specials, nor to classes or metaclasses.
The maximum number of dynamic instance variables that can be added to an object is 255. However, the maximum may be lower for classes with many instance variables, since an object cannot be changed to a large object by adding dynamic instance variables. so, more exactly, the actual limit for the number of dynamic instance variables is calculated:
(255 min: ((2034 - self class instSize) / 2)
To add a dynamic instance variable, set the value using:
anObject dynamicInstVarAt: nameSymbol put: value
For example, say you have an instance of Animal representing the Bald Eagle. Bald Eagles are an endangered species, so you might want to add the legal and conservation information to this instance, but not to other instances of Animals.
theBaldEagle dynamicInstVarAt: #legalStatus
put: 'Bald and Golden Eagle Protection Act'.
You can check what dynamic instance variables have been defined for an object:
topaz 1> printit
theBaldEagle dynamicInstanceVariables
%
an Array
#1 legalStatus
and retrieve the stored value for a dynamic instance variable:
topaz 1> printit
theBaldEagle dynamicInstVarAt: #legalStatus
%
Bald and Golden Eagle Protection Act
If the Bald Eagle was no longer protected and this information was no longer needed, you could remove the dynamic instance variable
theBaldEagle removeDynamicInstVar: #legalStatus
The name and data for dynamic instance variables are persisted in the repository like any other instance variable data. Dynamic instance variables allow you to add instance variables to instance of a class, without the need to migrate. However, dynamic instance variables are less efficient than named instance variables, and make for code that is more difficult to maintain.
In addition to implementation format and variables, there are other features of classes that can be, or must be, defined when the class is created. These are provided via subclass creation methods with additional keywords.
The subclass creation methods follow the form in Example 2.2.
Object subclass: 'Animal'
instVarNames: #('habitat' 'name' 'predator')
classVars: #('AllOfSpecies')
classInstVars: #('AllAnimals')
poolDictionaries: #()
inDictionary: UserGlobals
newVersionOf: Animal
description: 'Class describing Animals'
options: #()
The newVersionOf: allows you to create a new class that has the same classsHistory as an existing class; this is covered in detail in Chapter 10. See Versions of Classes.
The description: keyword allows you to provide documentation as part of the class definition. You can also explicitly set the comment after the class has been created by using the comment: method. For example:
Animal comment: 'Class describing Animal, created for the Programmers Guide'.
The options: keyword allows you to specify a collection of symbols to defined specific features of the new subclass. The options can include any of these:
See DbTransient for details. This option cannot be used in combination with #instancesNonPersistent or #instancesInvariant |
|
All instances of this class will be made invariant as soon as they are committed. If any class is defined with instancesInvariant, all its subclasses must also have instancesInvariant. Cannot be used in combination with #instancesNonPersistent or #dbTransient |
|
See Non-Persistent Classes for details. This option cannot be used in combination with #dbTransient or #instancesInvariant |
|
Log class creation, including expressions that are the same as an existing class and do not create a new class instance or version, to the gem log or linked topaz output using GsFile class>>gciLogServer: |
|
If this symbol is included, the class remains is modifiable after creation. No instances can be created until you make the class unmodifiable by sending it the message immediateInvariant. |
|
If this symbol is included, it must be first, and in this case options are not inherited from the superclass nor from an existing version of the class. This applies to the options #subclassesDisallowed, #disallowGciStore, #traverseByCallback, #dbTransient, #instancesNonPersistent, and #instancesInvariant |
|
For more details on class creation protocol, refer to methods in the image.
Note that subclasses creation protocol including the keywords inClassHistory:, isInvariant:, constraints:, isModifiable:, and instancesInvariant: may still appear, but are deprecated. Methods including these keywords should not be used.
For data that must not ever be changed, GemStone provides two ways to make objects invariant or unchangeable. These are object-level invariance, and class-level invariance.
Any object can be made invariant by sending it the message immediateInvariant (a method defined by class Object). This mechanism provides a form of write-protecting objects that is useful for maintaining the integrity of your database. Once immediateInvariant is sent to an object, no modifications can be made to any of the object’s instance variables, nor can the size or class of the object be changed. The immediateInvariant message takes effect immediately, but can be reversed by aborting the transaction in which it was sent. Once the transaction has been committed, you cannot reverse the effect of this message. The message isInvariant returns true if the receiver is invariant; false otherwise.
In class-level invariance, the definition of the class specifies that all instances of the class are invariant. Such an instance can be modified only during the transaction in which it is created. When the transaction is committed, the instance becomes invariant and no further modifications can be made to any of its instance variables, nor can the size or class of the object be changed. This mechanism is useful for supporting literals in methods and in other limited situations, but is generally more cumbersome than object-level invariance.
Class-level invariance can be specified during class creation by including the #instancesInvariant symbol in the options: keyword argument. You cannot also define the class with non-persistent instances (#instancesNonPersistent), nor with non-persistent instances variable data (#dbTransient).
The following example creates a subclass of Animal whose instances are invariant:
In some cases, you may want either objects or the instance variables of objects to not be persistent, that is, not be written to disk. For example, you may want to include session-dependent information that shouldn’t be read by another session, or data that is bulky and can be recreated easily. There are several ways to handle this.
You can define a class as having only non-persistent instances. This means that instances of this class cannot be committed, so you cannot include references to instances of non-persistent classes within a persistent data structure.
To create a class with non-persistent instances, in the options: keyword argument, include the symbol #instancesNonPersistent. You cannot also define the class with non-persistent instances variables (#dbTransient), nor with invariant instances (#instancesInvariant).
As discussed under KeySoftValueDictionary, GemStone provides a class called KeySoftValueDictionary, which allows you to manage non-persistent objects that are large and take time to create, but can be recreated whenever needed from small, readily available objects (tokens).
You cannot commit instances of a non-persistent class. If you attempt to do so, GemStone issues an error that indicates whether the object’s class or a superclass is non-persistent. (The non-persistent status of a class is inherited by all of its subclasses.)
To determine whether a class’s instances are non-persistent, you can send the following message:
theClass instancesNonPersistent
This message returns true if the class is non-persistent, false otherwise.
To make all instances of a class non-persistent, send the message:
theClass makeInstancesNonPersistent
Similarly, send this message to make all instances of a class persistent:
theClass makeInstancesPersistent
To make all instances of a class (and all of its subclasses) non-persistent, even if the class is non-modifiable:
ClassOrganizer makeInstancesNonPersistent: theClass
Similarly, you can send this message to make all instances of a class persistent, even if the class is non-modifiable:
ClassOrganizer makeInstancesPersistent: theClass
Classes can also be defined as DbTransient. Instances of classes that are DbTransient can be committed — that is, there is no error if they are committed — but their instance variables are not written to disk. This is useful if you need to encapsulate objects that should not be persistent, such as semaphores, within object structures that do need to be persistent and shared.
To create a class with DbTransient instances, in the options: keyword argument, include the symbol #dbTransient. You cannot also define the class with non-persistent instances (#instancesNonPersistent), nor with invariant instances (#instancesInvariant).
When a data structure containing an instance of a DbTransient class is committed, the instance variables of the DbTransient object are written to the repository as nil. Whenever a DbTransient object is read into a session, all of its instance variables are nil.
Since DbTransient instances are stored only in memory, they are affected by the in-memory GC operations. (See Managing VM Memory. Also see Chapter 11 of the System Administration Guide.)
If memory becomes low, the transient objects may be stubbed out of memory. When needed, it is re-read from the repository. However, all the instance variables will be nil after a re-read. To prevent losing non-nil instance variable values, you should keep a reference to DbTransient instances in session state.
Since the DbTransient object will remain in memory while referenced from session state, the reference from session state should be removed when the DbTransient object is no longer needed, to avoid filling up memory and causing an out of memory error.
Note that while DbTransient objects are only committed once (on creation), and so do not normally cause concurrency conflicts, if they are clustered the object will be written (still with all instance variables nil), and could potentially cause a concurrency conflict.
To set a class so all instances are DbTransient, send:
aClass makeInstancesDbTransient
aClass must be a non-indexable pointer class. This will cause any instance of aClass to be DbTransient. The change takes place immediately.
aClass makeInstancesNotDbTransient
will cause instances to be non-DbTransient, that is, allow instance variables to be written to disk.