KB0122

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.

  1. 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 } }

  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 }

  3. 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 }

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