How do I Change a Square into a Circle? Polymorphism and Dynamic Re-classification of Persistent Objects
Applies to: Caché Objects
Caché provides basic support for polymorphism. However, no mechanism is provided for converting an object of one type into another. For transient objects this is not much of a problem because the lifespan of the object is quite short, and if really necessary, it is usually acceptable to create a new instance of the desired type.
It is more of a problem with persistent objects. They often have a long lifetime and may well change type over time. For example, if the Doctor class is derived from the Person class and a person becomes a doctor, the class type of that instance should change from Person to Doctor.
The approach of creating a new instance is often not suitable for persistent objects because there may be many references to that instance from many other objects in the database.
The solution described here provides a mechanism for dynamically changing the type of a persistent object while retaining its persistent identity.
The general approach is to allow a new object of the desired type to be created with the same object Id as the original instance. The new object, when saved, will replace the old object. A method of the super-class is used to copy any properties which are common to both the old instance and the new instance.
Technique
The following instructions describe step-by-step how this is done for the classic polymorphism example, Shapes.
We have the following classes:
Class Shape Extends %Persistent [ ClassType = persistent, ProcedureBlock ]
{
Property colour As %String;
Property creationDateTime As %TimeStamp;
Method area() As %Library.Numeric [ Abstract ] { }
}
Class Circle Extends Shape [ ClassType = persistent, ProcedureBlock ]
{
Property radius As %Numeric;
Method area() As %Numeric
{
q 3.14159*(..radius**2)
}
}
Class Square Extends Shape [ ClassType = persistent, ProcedureBlock ]
{
Property side As %Numeric;
Method area() As %Numeric
{
q ..side**2
}
}
A method is added to the Shape class which copies the properties of the super-class from the old object instance to the new object instance. It also sets the object Id of the new object to that of the old object.
Method reclassify(oldObject)
{
s ..colour=oldObject.colour
s ..creationDateTime=oldObject.creationDateTime
;
d ..%IdSet(oldObject.%Id())
d oldObject.%DeleteId(oldObject.%Id())
q
}
A second method is added to the Shape class which is invoked when a new instance of this class, or any of its sub-classes, is created. It takes the old instance as an argument.
method %OnNew(oldObject) as %Library.Status
{
i $g(oldObject)="" q 1
d ..reclassify(oldObject)
;
q 1
}
To use this mechanism, suppose we have an existing instance of a square which we want to change into a circle. We can change this shape into a circle by setting oShape to a new instance of circle and at the same time passing in the old square instance as an argument to the %New() method:
USER>s oShape=##class(Square).%OpenId(15)
USER>zzw oShape ; this will only work if you have command line extensions installed
[12@User.Square] Id = 15
- colour = Black
- creationDateTime = 1900-01-01 00:00:00
- side = 2 (2.00)
USER>s oShape=##class(Circle).%New(oShape) ; A square becomes a circle!
USER>zzw oShape
[22@User.Circle] Id = 15
- colour = Black
- creationDateTime = 1900-01-01 00:00:00
- radius =
USER>w oShape.%Save()
1
Features
The %OnNew() method can be over-ridden in any of the sub-classes to implement specific rules about how an object of one type gets transformed into another type. For example, the rectangle class might implement the following:
method %OnNew(oldObject) as %Library.Status
{
i $g(oldObject)="" q 1
d ..reclassify(oldObject)
;
; Transform from Rectangle to Square
i oldObject.%ClassName(1)="User.Square" d
. s ..height=oldObject.side
. s ..width=oldObject.side
;
; Transform from Rectangle to Circle
i oldObject.%ClassName(1)="User.Circle" d
. s ..height=oldObject.radius
. s ..width=oldObject.radius
;
q 1
}
- This technique allows a sub-class instance to be transformed into an instance of its super-class. For example, a Doctor can be transformed into a Person and vice versa.
Limitations
- The data related to the old object is retained in the database; it does not get deleted by this technique. Other than taking up storage space, this does not appear to have any other consequences.
- The behavior of this technique with arrays, lists, one-to-many relationships and parent-child references needs further investigation.
- It is not possible to create multiple sub-class instances (eg a Person who is both a Patient and a Doctor).
- The behaviour of this technique with indexes needs further investigation. Indexes of sub-classes do not get deleted unless we call %DeleteId(), but this could fail because of referential integrity constraints.
Last reviewed: Nov 8, 2004
Back to Caché Knowledge Base