Consider the following class dictionary that defines 2 dynamic subclasses of Person, Student and Employee:
Person = <state> PersonState .
PersonState : Student | Employee .
Student = .
Employee = .
At run time, the Person object can be migrated to be an
Employee using the rset_[member] method that is generated
as part of the class by Demeter:
// Create a Person object that has a Student internal // representation Person * pPerson = new Person( new Student() ); ... // Now dynamically change the subclass of Person from // Student to Employee using rset delete pPerson->rset_state( new Employee() );The method rset_state() takes the new representation for the Person object, storing it in state and returns the previous object representation. Note the deletion of the return value form rset_state(). This is to insure that the Student object is deleted.
Adaptive programs are robust with respect to the class structure changes implied by the Adaptive Dynamic Subclassing Pattern. (See the Demeter book for more information about Demeter.) For example, *from* Company *to* Student will count the number of students employed by a company whether Student is a subclass of Person or not. Also, *from* Company *via* *to* Student with a wrapper at Person referencing Person information will also work in both cases.
An adaptive program defined as a set of propagation patterns will devote a subset of its patterns to handling subclass migration for those dynamic subclasses of interest.
Refering back to the Person example, at run time you want to keep the object id, i.e. the pointer to the person object, but change the Person object from a Student internal representation to an Employee internal representation.
Also, note that the Student object has the same identifier as the Person object. If you were to count the number of Persons in a group of 5 Students, the answer would be five. If you were to migrate all five Students to be Employees, the number of Persons would still be five and they would be the same Persons as the five Students, but now acting as Employees.
Vehicle : Truck | Car . Truck = . Car = .To convert this class dictionary to use dynamic subclasses, you define a new abstract class for the static subclasses of Vehicle, call it VehicleType. Then make Vehicle a construction class with a data member called vehicleType that is of type VehicleType:
Vehicle = <vehicleType> VehicleType .
VehicleType : Car | Truck .
Car = .
Truck = .
It is possible to create other dynamic subclasses of Vehicle that
represent other partitions of functionality. For example, we may
want to represent different engine kinds that can be coupled with
the different vehicle types:
Vehicle =It is now possible with this new class dictionary to create a Vehicle object that represents a Gas Car or a Deisel Truck. It is also possible to migrate between these combinations.<engineKind>EngineKind<vehicleType>VehicleType . EngineKind : Gas | Deisel | Electric . Gas = . Deisel = . Electric . VehicleType : Car | Truck . Car = . Truck = .
For example, at run time a Vehicle object with an internal representation of Gas Car can be migrated to be an Elecletric Car or to being a Deisel Truck without losing the Vehicle object id.
This class dictionary can also be modified to add additional classes for defining Cars and Trucks. The subclasses of Car and Truck replace Car and Truck as dynamic subclasses of Vehicle. Car and Truck are now on the path between Vehicle and a subset of its dynamic subclasses, i.e. those derived from Car and Truck:
Vehicle =In addition to the propagation patterns that define core functionality, an application will want to define a set of propagation patterns to handle all the cases of object migration that apply to the application. These are called Migration Propagation Patterns<engineKind>EngineKind<vehicleType>VehicleType . EngineKind : Gas | Deisel | Electric *common*<amtFuel>DemNumber . VehicleType : Car | Truck *common*<numDoors>DemNumber . Gas = "gas" . Deisel = "deisel" . Electric = "electric" . Car : SportsCar | FourDoorSedan | StationWagon . Truck : PickupTruck | FourWDriveTruck | GarbageTruck . SportsCar = "sports-car" . FourDoorSedan = "4-door-sedan" . StationWagon = "station-wagon" . PickupTruck = "pickup-truck" . FourWDriveTruck = "4-wheel-drive-truck" . GarbageTruck = "garbage-truck" .
To illustrate, here are two migration propagation patterns used by an application that is modeling the fuel efficiency of a sports car that have both an electric and gas engine.
The application wants to be able to dynamically change the sports car's engine from Gas to Electric and back again to determine the optimal combination. The patterns are passed a Number object for setting the fuel amount of the new engine and a Number object for storing the previous engines fuel amount.
*operation* void SwitchToGas( DemNumber * pGasolene, DemNumber * pVoltage )
*wrapper* Vehicle
(@
EngineKind * pOldEngine;
pOldEngine = rset_engineKind( new Gas() );
engineKind->set_amtFuel(pGasolene);
pVoltage = pOldEngine->get_amtFuel();
delete pOldEngine;
@)
*operation* void SwitchToElectric( DemNumber * pVoltage, DemNumber * pGasolene )
*wrapper* Vehicle
(@
EngineKind * pOldEngine;
pOldEngine = rset_engineKind( new Electric() );
engineKind->set_amtFuel( pVoltage );
pGasolene = pOldEngine->get_amtFuel();
delete pOldEngine;
@)
Another Design Pattern that would be useful when using
dynamic subclasses is the Momento Design Pattern that is
documented in 'Design Patterns' by the GOF. This pattern
describes a mechanism for taking the state of an object.
An adaptive program may wish to use this mechanism to save the state of a subclass before migrating to another so that the state can be restored when the object migrates back to that subclass.