To achieve abstraction over classes, we need an object representing the class at run time. Such metaclass objects would have fields and methods associated with the class, such as instantiation counts or constructors. We would then abstract over metaclass objects with regular interfaces, treat classes as values, and even allow parameterization by a class.
In a distributed or other dynamic configurable systems, it is quite often that we need to create an object from an unknown class. Such class can be a new class plugged into the system recently from a dynamic software component.
The Abstract Factory pattern provides an interface for creating families of products over an abstract class. At run time, user can call the virtual creation method in the abstract factory, which is bound to a concrete factory at run time. When a dynamic software component is plugged in, it must make sure for the success of the binding.
Consider the following example which is extracted from the MagicWand system and reproduced in C++:
class AbstractScrollable;
class Scale: public AbstractScrollable;
class ScrollBar: public AbstractScrollable;
class SlidingList: public AbstractScrollable;
class GeoRuler: public AbstractScrollable;
...
|
The abstract class AbstractScrollable defines an interface for all scrollable objects that have two internal states between 0 and 1 to indicate its current position and its actual coverage. For example, a sliding list that has a value (0.3, 0.25) would meaning that the current position of the sliding list is focused on the item at the position of 0.3 of the entire length of the sliding list, and it has a coverage of 0.25, i.e. only a quarter of the items can be shown in the list.
Since MagicWand must support dynamic plug-ins, it should allow a user to create scrollable object from a new class in a dynamic software component.
In C++, this would be implemented by a class factory:
class AbstractScrollableFactory: public ClassFactory
{
public:
virtual AbstractScrollable & Create
( char* name, Object* parent,
);
AbstractScrollableFactory ()
:ClassFactory("AbstractScrollable", classFactory {};
} abstractScrollableFactory;
|
Later, we assume that a dynamic software component that contains the following class be plugged in:
class TimingBar: public AbstractScrollable;
class TimingBarFactory: public ClassFactory
{
public:
virtual TimingBar & Create
( char* name, Object* parent,
);
TimingBarFactory ()
:ClassFactory("TimingBar", abstractScrollableFactory) {};
} timingBarFactory;
|
When the component is plugged in, pplications should be able to access the class factory of TimingBar to create a new object.
In a real system, the class factory should do much more than that. It should at least register this class so the the system class browser will automatically includes the class for user to create the object. It should also register those public methods and attributes that should be handled for an object dynamically. With these registered methods and attributes, the system should be able to create a standard object inspector dialogue to manipulate the object, or, if the customized dialogue format is presented, it should present the customized dialogue. These requirements may make the design of class factories very complicated.
The major disadvantage of this class factory pattern is that each concrete subclass must be mirrored by a class factory class. Each the class factory must have an object (which is actually a singleton) to represent the class. It is rather clumsy to write each class factory for each concrete class for registering the class and the methods and attributes that can be handled dynamically.
With a proper support of metaclass, the trouble mentioned above can be eliminated. Developers just write the classes they should develop. They no longer to worry about a separate class hierarchy and all the other details about the register of a class.
In Transframe, classes are first-class objects. They can be passed as values. We can first develop a class called Object.
class Object
{
// public by default
class method // a member class to define method call instances
{
meta:
enter () { /* register the method in the enclosing class */ };
// a meta constructor for creating and initializing the method class
}
meta: // public by default
function Create (name: char[], parent: Object): Object;
// a meta constructor to create instances
enter () { /* register the class in the class manager*/ };
// a meta constructor for creating and initializing the Object class
meta private:
method_list: List of Method;
}
|
The member class method defines how a method instance (activity) can be created and be executed within the object. It has a class constructor. Whenever a concrete subclass of Method is defined, this class constructor will be called automatically to register the method into the method_list, which is a meta member of its enclosing class, i.e. the Object class.
The class factory pattern is no longer required. Assume that we want to create a scrollbar class, we can write the class simply by inheriting the class Object as well as its method:
interface class Scrollable is Object
{
method GetScrollState (): (position: float; coverage: float);
method SetScrollState (position: float; coverage: float);
}
class SlidingList is Scrollable
{
method GetScrollState (): (position: float; coverage: float) { ... };
method SetScrollState (position: float; coverage: float) { ... };
}
|
The interface class Scrollable defines a scrollable interface which contains two methods. SlidingList is a concrete subclass of Scrollable. Since it is a subclass of Object, SlidingList will be automatically registered in the system's class manager. GetScrollState and SetScrollState are subclasses of method, they will be registered automatically into the class SlidingList.
Consider the following usage:
// assume that class_icon be a user picked icon from a class browser
object_class: type of Object = class_icon.GetClass();
object_instance: Object =
object_class ? object_class.Create(DefaultName, CurrentFrame)
: 0;
|
The object can be created from a class that is dynamically located by user's run time selection. Suppose the object is successfuly created in the current frame which is an visual object container such as window, list, or spreadsheet cell, the object will be displayed under the configuration of the current frame.
Using an inspect tool to pick the object will tell the system to create inspect dialog to manipulate the object interactively. The method and attributes are automatically registered in the class, which provides a sufficient information for the system to handle dynamic method calls and attribute settings. For example, user can improvise a script for the object and see how the object response. MagicWand also provides possibility for users to specify specific object inspectors, for example, using the color space cube the color picker. MagicWand can load the source specification (a subset of Transframe) and interpret it in a similar way as a web browser loads the the HTML file.