This paper describes the object-oriented scripting language XOTCL and ..... For example the description of an oval carpet and a desk can nest inside of the.
XOT CL, an Object-Oriented Scripting Language? Gustaf Neumann and Uwe Zdun University of Essen, Germany Information Systems and Software Techniques gustaf.neumann,uwe.zdun @uni-essen.de
f
g
Abstract. This paper describes the object-oriented scripting language XOT CL and provides introductory examples on its usage. XOT CL (Extended OT CL ) is an extension of MIT’s OT CL [26]. OT CL implements dynamic and introspective language support for object-orientation modeled after the Common Lisp Object System [3]. The XOT CL extensions focus on the construction, management, and adaptation of complex systems based on higher level constructs such as design patterns. In general, scripting languages offer benefits like dynamic extensibility and dynamic typing with automatic conversion that make them well suited for rapid application development [20]. Since they provide easy integration of components written in other languages (like C), generic component frameworks can be flexibly used in applications. This leads to a high degree of software reuse. The scripting code functions as a “glue” between the components. In order to combine the benefits of scripting languages with advanced objectoriented techniques, we extended OT CL in various ways: We integrated the OT CL object system with a namespace concept to allow nested classes and object aggregations, and we introduced assertions and meta-data to improve reliability and self-documentation. In order to improve flexibility of mix-in methods, we enhanced the object model with per-object mix-ins, which enable objects to access several addition-classes. Finally, we developed the filter approach as a means for an intuitive and powerful language support for the instantiation of large program structures like design patterns, that cannot be realized through classes alone.
1 Introduction XOT CL (pronounced exotickle) is an extension of the object-oriented scripting language OT CL [26] which itself extends T CL (Tool Command Language [19]) with objectorientation. Similar to pure T CL, our extensions can be used from a shell or as a library accessable through a C-API. XOT CL is a standard T CL extension that can be dynamically loaded into every T CL compliant environment such as the shell tclsh or Wafe [17]. In this paper we will present only the usage of XOT CL through the shell interface. The shell can be invoked with a parameter specifying a script, or it can be used interactively. All T CL commands and T CL extensions can be used from the shell. A central property of T CL is the use of strings as the only representation of data. For that
reason it offers a dynamic type system with automatic conversion. All integrated components (application specific extensions typically written in C) use the same string interface for argument passing and therefore they automatically fit together. The components can be reused in unpredicted situations without change. In [20] and [18] it is pointed out that the evolving component frameworks have proven to provide a high degree of code reuse, a rapid application development and an ease of use. It is argued that application developers may concentrate primarily on the application task, rather than investing efforts in fitting components together. Therefore, in certain applications, where the flexible rearrangement of components is important, scripting languages are very useful for a fast and high-quality development of software. An example of such an application is the extensible web browser ? Submitted for publication, Nov. 98.
2
Gustaf Neumann, Uwe Zdun
Cineast [11], which is implemented in Wafe/XOT CL in less then 3000 lines of code. Hatton [9] points out that the use of object-orientation in languages like C++ does not fit the human reasoning process very well. In [18] we point out that the identified deficiencies do not apply on object-oriented scripting languages. T CL is equipped with appropriate functionalities like dynamic typing, dynamic extensibility and read/write introspection, that ease the glueing process. Most other object-oriented T CL -extensions do not support these abilities in their language constructs very well, since they integrate foreign language concepts and syntactic elements into T CL that encourage a programming style in structured blocks (like in C++) and they have a rigid and static class concept (e.g. [14, 7]). OT CL preserves and extends important features of T CL , like dynamic extensibility and introspection. It offers object-orientation with encapsulation of data and operations, single and multiple inheritance, a three level class system based on meta-classes, and method chaining. Instead of a protection mechanism OT CL provides rich read/write introspection facilities, which allow to change all relationships dynamically. Wetherall and Lindblad provide in [26] a more detailed presentation of OT CL and its design considerations.
Extended OTcl dynamic aggregations assertions meta-data per-object mix-ins filter
OTcl object-orientation: encapsulation inheritance multiple inheritance method chaining meta-classes read/write introspection dynamic extensibility
Tcl namespaces Tcl introspection extensibility
Fig. 1. Language Extensions of XOT CL
The OT CL properties described above provide a good basis for our work (see Figure 1). The XOT CL extensions focus on mechanisms to manage the complexity that may occur in large object-oriented systems, especially when these systems have to be adapted for certain purposes. Such situations occur frequently in the context of scripting languages. In particular we added the following support: – Namespace support, to (a) reduce the interference of independently developed program structures and to allow structured classes and (b) to provide dynamic aggregations through nested namespaces (objects). – Assertions, to reduce the interface and the reliability problems caused by dynamic typing and, therefore, to ease the combination of many components. – Meta-data, to enhance self-documentation of objects and classes. – Per-object mix-ins, as a means to improve flexibility of mix-in methods by giving an object access to several different addition-classes, which may be changed dynamically. – Filters as a means of abstractions over method invocations to implement large program structures, like design patterns. The implemented extensions lead to additional functionality and an improved performance. However, we had to introduce a few incompatibilities to OT CL, which are discussed in the following Section 2. The later sections of this paper will discuss the new language constructs in XOT CL focusing on their usage (shown with illustrative examples). A deeper discussion of the principles behind these constructs especially with the respect of design
XOT CL , an Object-Oriented Scripting Language
3
patterns can be found in [18, 27]. This paper assumes basic familiarity with T CL. For an introduction to T CL we recommend [21].
2 Language Constructs derived from MIT Object T CL (OT CL) As stated above, MIT Object Tcl [26] is an object-oriented extension of the Tool Command Language (T CL) [19]. OT CL uses an object command approach which follows the conventions and style of T CL. This section describes the basic functionality of XOT CL, that has been adopted from OT CL. This includes the commands to create objects and classes, the concepts of meta-classes, the inheritance model, mix-ins and dynamics. For the sake of compatibility we tried to preserve the realization of this functionalities, but in order to gain the maximum benefit from our extensions we had to introduce a few changes here. 2.1 Objects The command Object is used to create new objects. It provides access to the class Object, which holds the common features of all objects. Objects are always instances of classes, therefore, objects defined by Object are (initially) instances of the class named Object. But since these objects have no user-defined type, they may be referred to as singular objects. Every object can be dynamically adapted with (new) methods or variables at any time during its existence. The command Object receives the name of the new object as the first argument. As in OT CL for every object a new T CL -command with the name of the object is created, which allows to access the new object. Once the object is created it can be used like a normal T CL -command using sub-commands to access the object’s methods. Therefore, this form of access is called object-command approach. As the first example, we want to create an object which holds the information of an ovalOffice. The object ovalOffice is created by: Object ovalOffice
;# short form to create an object
In fact this is only a short form of a call to the create instance method, which is applied automatically when an unknown name is given to a classes command. Object create ovalOffice
;# explicit invocation of create
The method create firstly allocates memory for the new instance and calls then the constructor init, which has to be an instance method of the class (this is discussed in detail in Section 2.2). The destruction of an object is handled by the destructor, the destroy instance method. The object ovalOffice can be deleted by: ovalOffice destroy
The objects can be used immediately after creation. The class Object provides a range of predefined instance methods accessible for all objects, compactly presented in Table 1 (the last row indicates extensions relative to OT CL). The commands marked with “new” are discussed later in this paper. Instance Variables and Methods The set instance method can be used to create, set or query instance variables of an object. For example ovalOffice set carpetColor golden
creates a new instance variable named carpetColor on the object ovalOffice and sets its value to golden. If this instance variable would have had existed before, its value would be overwritten by this call. If the value parameter is omitted, the set method can be used to query the value of the instance variable (similar to plain T CL). Internally, all XOT CLvariables are stored as T CL-variables. The unset instance method deletes variables from an object. For example the variable carpetColor can be deleted by:
4
Gustaf Neumann, Uwe Zdun class
Set the object’s class.
unchanged
destroy
Delete the object.
extended
next
Mix-in next shadowed method along the prece- extended dence order.
proc
Define method for this object.
extended
set
Define or read a variable of the object.
unchanged
instvar
Link a variable to the current procedure’s scope.
unchanged
info
Reading introspection.
extended
mixins
Define per-object mix-in classes.
new
autoname
Automatic generation of new names.
new
invar
Define object invariants.
new
check
Checking of assertions.
new
metadata
Definition of meta-data.
new
Table 1. Basic Object Methods in XOT CL
ovalOffice unset carpetColor
Methods in XOT CL resemble T CL-procedures. The methods defined for an object are called “procs”. On contrary, instance methods defined on classes are called instprocs (see Section 2.2). A new proc is defined for the object by the built-in method proc by specifying the name, the arguments and the body of the method, which is executed when the proc is called. ovalOffice proc enter {index personName} { [self] set persons($index) $personName }
Once the method is defined it can be used like the predefined methods: ovalOffice enter 1 Bill
In the body of the proc above, an instance variable (an associative array) is set using the specified values. Instead of hard-coding the object name, the predefined command self is used to determine the name of the current object. In general the command self can be used in three different ways to access information about the current object or invocation: – self (without parameters) returns the name of the object, which is currently executed. – self class returns the name of the class, which holds the currently executing instproc (see Section 2.2). Note, that this may be different to the class of the current object. – self proc returns the name of the currently executing method (proc or instproc). Note, that there is a difference to the realization of these object informations to OT CL. XOT CL uses the command self to obtain this information, while OT CL uses three automatically set variables for this purpose ( self, class, and proc). This change was necessary in order to make the internal calling conventions of XOT CL-methods compatible with T CL-procedures, which has the advantage that the methods are accessible in XOT CL via namespace-paths (see Section 3). In order to obtain compatibility, XOT CL provides the compilation option AUTOVARS to set these variables automatically, but this has a slight performance disadvantage. Like all T CL-procedures, each XOT CL-method has its own variable scope, in which local variables can be defined. The variables that are valid in this scope are – the formal parameters passed to the method, and
XOT CL , an Object-Oriented Scripting Language
5
– variables created in or imported into this scope. The most important example for importing variables are instance variables. As shown above, instance variables can be accessed via the set method. Additionally they can be imported into the local scope through the instvar method, which is more convenient for the programmer, since they appear as ordinary T CL-variables. The following definition of the proc enter is equivalent to the previous one, and uses the instvar method to import the instance variable. ovalOffice proc enter {index personName} { [self] instvar persons set persons($index) $personName }
Note, that by using for example self class instead of self in front of instvar, class variables can be imported by the same mechanism. Information on Objects The reading introspection abilities of XOT CL are packed compactly into the info instance method which is available for objects and classes. The options of this method, which are valid for objects, are summarized in Table 2. More options to info are presented later in context of our new extensions.
objName info args method
Returns the arguments of the specified method.
objName info body method
Returns the body of the specified method.
objName info class ?name?
If name is specified, it is compared with the class name of the object and 1 or 0 is returned upon success or failure. If name is not specified, the name of the class of objName is returned.
objName info commands ?pattern?
Returns all commands defined on the object (matching pattern if it is specified).
objName info default method arg var
Returns 1 if the argument arg of the method method has a default value, otherwise 0. If it exists the default value is stored in var.
objName info procs ?pattern?
Returns all procs defined on the object (matching pattern if it is specified)
objName info vars ?pattern?
Returns all variables defined on the object (matching pattern if it is specified).
Table 2. The Object info Options
Furthermore, XOT CL offers writing introspection. All introspection options are changeable (through the appropriate instance methods, e.g. vars through the set and unset instance methods, or the class can be modified thought the class instance method). 2.2 Classes The XOT CL Class System In XOT CL every object is associated with a class over the class relationship. Classes are ordered by the relationship superclass in a directed acyclic graph. The root of the class hierarchy is the class Object. A single object can be instantiated directly from this class. Figure 2 shows the basic relationships between objects and classes in XOT CL.
6
Gustaf Neumann, Uwe Zdun Meta-Class Objects
Class Objects
Objects
class class
Class
superclass
Object
class
object
superclass
Fig. 2. The XOT CL Class System [26]
Classes are in XOT CL special objects with the purpose of managing other objects. “Managing” means that a class provides methods to create instances, and that it provides methods for its instances (defines its behavior). For this purpose classes contain a repository of methods (“instprocs”) for its instances. Furthermore, a class provides a super-class relationship that supports single and multiple inheritance. The instance methods common to all objects are defined in the root class Object (predefined or user defined). Since a class is a special (managing) kind of object it is managed itself by a special class called “metaclass” (which manages itself). One interesting aspect of meta-classes is that by providing a constructor pre-configured classes can be derived. New user-defined meta-classes can be derived from the predefined meta-class Class in order to restrict or enhance the abilities of the classes that they manage. All inter-object and inter-class relationships (such as class, superclass) are completely dynamic and can be changed at arbitrary times with immediate effect. This is discussed in detailed in Section 2.3.
create
Create an instance of the class/meta-class.
unchanged
instproc
Define an instance method.
extended
superclass
Set the classes’ super-class.
unchanged
filter
Add or remove filter methods.
new
classinvar
Definition of class invariants.
new
Table 3. Basic Class Methods in XOT CL
Since classes are also objects, all methods applicable for objects (Table 1) can be applied on the class objects as well. The additional basic methods for classes are summarized in Table 3. Creating Classes and deriving Instances The way to create a new class is very similar to the way an object instance is created, because the creation of a class is actually an instantiation of a meta-class. During the instantiation the according constructor is called automatically. The class object can store variables or procs exactly like an ordinary instance object. The set method used on the class creates an instance variable in the class object. Such a variable is sometimes referred to as a class variable. The following lines can be used to define a new class Room, to create an instance for this class, and to set a class variable: Class Room Room office Room set owner Al
;# define the new class Room ;# create an object instance for this class ;# set a variable in the class object
XOT CL , an Object-Oriented Scripting Language
7
Instance Methods The most important aspect of a class in XOT CL is that it stores methods for its instance objects. These instance methods are called instprocs. The definition of instance methods is similar to the definition of object methods, but uses the keyword instproc (instead of proc). Room instproc enter {} { [self] instvar personsInRoom incr personsInRoom } A special instproc is the constructor init, which was mentioned already. When an instance is derived from a class, the instproc init of the new class is invoked automatically. Note
that this mechanism is used for the creation of ordinary objects, classes and meta-classes. The constructor is responsible for all initialization tasks, when a new instance of the class is derived. In the example below the constructor sets an instance variable in the created object. The call to self returns the object name of the instance. Room instproc init args { [self] set personsInRoom 0 }
Information on Classes Since classes are objects, all introspection options for objects (Section 2.1) can be used. Furthermore, the information about class relationships, instances and instance methods can be obtained through the additional info options summarized in Table 4. Exactly the same introspection mechanisms are available for classes and metaclasses.
ClassName info heritage ?pattern?
Returns the list of all classes in the precedence order of the class hierarchy (matching pattern if it is specified)
ClassName info instances ?pattern?
Returns the list of the instances (matching pattern if it is specified)
ClassName info instargs method
Returns the arguments of the specified method.
ClassName info instbody method
Returns the body of the specified method.
ClassName info instcommands ?pattern?
Returns all commands defined on the class (matching pattern if it is specified)
ClassName info subclass ?name?
If name is specified the command tests whether the ClassName has an immediate sub-class with this name. If name is not specified a list of all immediate sub-classes is returned.
ClassName info superclass ?name?
If name is specified the command tests whether the ClassName has an immediate super-class with this name. If name is not specified a list of all immediate super-classes is returned.
Table 4. The additional info Options for Classes
Inheritance One important requirement for object-orientation is inheritance [25]. XOT CL supports both, single and multiple inheritance. Classes are defined in a directed acyclic class graph constructed by the superclass relationship. Every user defined class is a sub-class (not necessarily an immediate sub-class) of the class Object. The inheritance relationships is defined by the instance method superclass (which is defined in the meta-class Class).
8
Gustaf Neumann, Uwe Zdun
Class Room Class OvalOffice -superclass Room In this example OvalOffice inherits methods from Room, Room inherits from Object.
The notation of method invocation with a leading dash is actually a short form of: Class OvalOffice OvalOffice superclass Room
An arbitrary number of method invocations using this short form can be appended to every command that creates objects. In the following example the definition is refined such that OvalOffice inherits from OvalRoom and Office. For multiple inheritance the superclass method receives a list of super-classes. Class Room Class OvalRoom -superclass Room Class Office Class OvalOffice -superclass {OvalRoom Office} OvalOffice o0
An inherent problem of multiple inheritance is the problem of name resolution, when for example both super-classes contain an instance method with the same name. XOT CL provides an intuitive and unambiguous approach for name resolution by defining the precedence order along a “next-path”. Firstly, the class of the object o0 is searched, which is OvalOffice in the example. Then the super-classes are searched (here OvalRoom before Office). Each branch is searched completely, before changing to the next branch. If there are multiple super-classes defined, they are searched in the order of their definition. Therefore Room is searched before the Office branch is visited. Finally, the top of the hierarchy, the class Object, is searched (see Figure 3).
Object
next
Room
next
Office
next OvalRoom
next
OvalOffice
Fig. 3. The Classes for the Room Example and the implied Next-Path
Destruction of Classes Destruction of classes is handled by the destroy instance method of Class (resembling object destruction). The destruction of super-classes does not destruct the sub-classes. The super-class is simply removed from the sub-classes’ super-class lists. If all super-classes are destroyed or removed, the new super-class is Object. The destruction of the class of an object does neither delete the object nor leave it without class. Since in XOT CL class dynamics is encouraged, that would make little sense.
XOT CL , an Object-Oriented Scripting Language
9
When a class of an object is deleted the class relationship of the object is set to Object. Instead of providing “empty” class- and super-class-relationships, the most general class is used. Note, that this is different to OT CL, where the destruction of a class destroys all instances and where an object with an empty super-class can be the result of the deletion. Method Chaining A special feature of XOT CL is the method chaining without explicit naming of the “mix-in” method. It allows to mix the same-named (or “shadowed”) superclass methods into the current method (modeled after CLOS [3]). The previously described next-path is the basis for this functionality. A method can invoke explicitly the shadowed methods by the predefined instance method next. When there exists a shadowed method it is “mixed into” the execution of the current method. The class on which the shadowed method is found is called a mix-in. Note, that the usage of next in XOT CL is different to OT CL as well. In XOT CL, a call of next without arguments can be used to call the shadowed methods with the same arguments (which is the most common case). When arguments should be changed for the shadowed methods, they can be provided. In the rare case that the shadowed method should receive no argument, the flag --noArgs can be used. In OT CL it is necessary to provide the full argument list for every invocation explicitly. In the following example we provide a constructor for all defined classes. In order to call all constructors along the next-path, the method next is used. Room instproc init args { [self] set roomNumber 0 [self] next } OvalRoom instproc init args { [self] set diameter-x 0 [self] set diameter-y 0 [self] next } Office instproc init args { [self] set deskType metal [self] next }
When an object of class OvalOffice is created the four instance variables diameter-x, diameter-y, roomNumber, and deskType are defined with default values. At runtime the definition of the instance variables occurs in the mentioned order. This ordering mechanism is used for all instprocs. 2.3 Dynamic Class and Super-Class Relationships Unlike languages with a static class concept, XOT CL supports dynamic class relationships. At any time the class graph may be changed entirely using the superclass method, or an object may change its class through the class method. Suppose a set of rooms should be displayed to a screen, but it is not determined, whether the user of the system has the ability for graphical output. Two classes TextOutput and GraphicalOutput may be defined, which handle the output. Both contain an instproc paint which does the painting of the rooms on the chosen display type. The common output requirements are handled by a derived class RoomOutput which calls the paint method of the super-class using next. In XOT CL we can use the superclass-method to change between the two representations easily: Class TextOutput TextOutput instproc paint args { # do the textual output ... }
10
Gustaf Neumann, Uwe Zdun Class GraphicalOutput GraphicalOutput instproc paint args { # do the painting ... } # initially we use textual output Class RoomOutput -superclass TextOutput RoomOutput instproc paint args { # do the common computations for painting ... [self] next; # and call the actual output variant } # the users decides to change to graphical output RoomOutput superclass GraphicalOutput
The relationship between object and class can be changed dynamically as well. This allows an elegant implementation of a life-cycle or of objects changing roles. These changes can be achieved without loosing the object’s identity, its inner state and per-object-specializations through procs. The instance method class provides this functionality. Imagine for example an agent for a virtual world. Agents may be placeholders for persons, who interactively travel the world, or programs, which act automatically. When a person uses an agent interactively and decides at run-time to delegate a task to an agent, the agent’s nature may change from interactive to automatic. The identity and the local state of the agent (e.g. the results of already finished tasks) remain unchanged by a modification of the class. This is can be implemented by changing a class relationship: Class Agent Class AutomaticAgent -superclass Agent Class InteractiveAgent -superclass Agent # create a new agent for a person InteractiveAgent agent1 # the interactive agent does something ... # ...and decides the change to an automatic agent agent1 class AutomaticAgent
2.4 Meta-Classes As seen already, a special kind of classes are meta-classes. They are classes that contain the features common to all derived classes. We have already introduced the predefined meta-class Class. A user-defined meta-class can be derived from an existing meta-class, like a normal class-definition, by defining Class as super-class, e.g.: Class myMetaClass -superclass Class
Now we have created a new meta-class myMetaClass, which has all the abilities of metaclasses. That means, that the programmer is able to specify new class features or override old ones. Afterwards he may instantiate these into new classes. This is a very powerful language feature, since it allows to give some classes further abilities then the others (or to restrict classes). This way large program structures, like design patterns, may be instantiated. The meta-class may hold the generic parts of the structures. They allow to form libraries of such structures very easily. In [18] this topic is covered more deeply, examples for the instantiation of meta-classes may be found there and in Section 7.3. 2.5 Automatic Name Creation A new feature in the XOT CL language are automatically created names. The XOT CL autoname instance method provides a simple way to take this task out of the responsibility of the programmer. An example is a loop which creates ten new agent-objects with an automatically created name (beginning with myAgent):
XOT CL , an Object-Oriented Scripting Language
11
Agent proc create10 {} { for {set i 0} {$i < 10} {incr i} { Agent [[self] autoname incr myAgents] } }
3 Namespaces Most object-oriented analysis and design methods are based on the concepts of generalization and aggregation [23]. Generalization is achieved through class hierarchies and inheritance, while static aggregation is provided through embedding. Since version 8.0 T CL offers a namespace concept which can be used as a mechanism to provide dynamic aggregations. A namespace provides an encapsulation of variable and procedure names in order to prevent unwanted name collisions with other system components. Each namespace has a unique identifier which becomes part of the fully qualified variable and procedure names. Namespaces are therefore already object-based in the terminology of Wegner [25]. OT CL is object-oriented since it offers classes and class inheritance. Its objects are also namespaces, but an object is more than only a namespace. Therefore, two incompatible namespace concepts have existed in OT CL in parallel. Extended OT CL combines the namespace concept of T CL with the object concept of OT CL . Every object and every class in XOT CL is implemented as a separate T CL namespace. The biggest benefit of this design decision aside from performance advantages is the ability to aggregate objects and nest classes. Contrary in OT CL every object has a global identifier. Through the introspection abilities of namespaces nested classes are also traceable at runtime and can be changed dynamically. In XOT CL objects are allowed to contain nested objects, which are real aggregates of the containing object. 3.1 Nested Classes The notation for nested classes follows the syntax of T CL namespaces by using “::” as a delimiter. For example the description of an oval carpet and a desk can nest inside of the class OvalOffice: Class OvalOffice # general carpet Class Carpet Class OvalOffice::Desk # special oval carpet -- no name collision Class OvalOffice::Carpet -superclass ::Carpet
Nested classes can be used exactly like ordinary classes, a user can sub-class it, derive instances, etc. The information about the nesting structur of classes is available through the info instance method: ClassName info classchildren ClassName info classparent The classchildren option returns a list of children, if one or more exist, otherwise it returns an empty string. classparent results in the name of the parent class, if the class
is nested. Since nested classes are realized through namespaces, all functionality offered by T CL’s namespace command is usable from XOT CL as well. 3.2 Dynamic Object Aggregation The nested classes only provide an aggregation of the description, not of the runtimestructures. We have pointed out the difference of object and class in XOT CL. Because of the splitting of a class into class and class object it is possible to give each object its own namespace. The internal implementation of objects allow that they contain nested objects, which are aggregates of the containing object. Suppose an object of the class Agent should aggregate some property objects of an agent, such as head and body:
12
Gustaf Neumann, Uwe Zdun Class Agent Agent myAgent Class Agent::Head Class Agent::Body
Agent::Head ::myAgent::myHead Agent::Body ::myAgent::myBody Now the objects myHead and myBody are part of the myAgent object and they are accessible through a qualification using “::” (or through T CL’s namespace command). But in the
common case they will be accessed, as introduced so far: the explicit full qualification is not necessary when such variables are being accessed from within XOT CL methods, since the object changes to its namespace. The information about the part-of relationship of objects can be obtained exactly the same way as for classes through the info method interface: objName info children objName info parent
3.3 Short Form for Object Aggregation and Class Nesting The self command returns fully qualified names. So all usages of self automatically refer to the correct qualified namespace. For the ease of use in procs (and instprocs) we have introduced a new syntax expressing the aggregation directly. That avoids long qualified names: objName ClassName aggregatedObj
This creates a new object aggregatedObj of the type ClassName aggregated on the object objName. Similarly this is also allowed on classes: ClassName MetaClassName nestedClass
This creates a new class nestedClass of the type MetaClassName nested in the Class ClassName. 3.4 Relationship between Class Nesting and Object Aggregation The classes Head and Body are children of the Agent class. It is likely that all agents, interactive or not, have properties for head and body. This implies a static or predetermined relationship between class nesting and object aggregation. Such predeterminations do not exist in XOT CL, but are simply build, when specifying the relationship in the constructor, e.g.: Agent instproc init args { [self] ::Agent::Head myHead [self] ::Agent::Body myBody }
Now all agents derived from the class have the two property objects aggregated after creation. But still they are changeable in a dynamical manner, e.g. with: Agent myAgent myAgent::myHead destroy
The agent turns into a headless agent. In companion of the introspection mechanisms such constructions could be very useful. Suppose, that in the virtual world the agents heads may be slashed from their bodies. The graphical system simply needs to ask with info children on the agent’s object, whether it has a head or not and can choose the appropriate graphical representation. Note, that the not existing relationship allows a great deal of freedom and dynamics, which goes together with the ideas behind OT CL, e.g. like the renunciation of protection mechanisms. This policy in programming language design means, on the one hand, ease of programming and more expressiveness, but, on the other hand, it contains no protection against bad software architectures or programming style. We believe that no such mechanisms could hinder the programmer to do silly things, so our policy was, to give the programmer rather more powerful constructs then to make decisions in his place.
XOT CL , an Object-Oriented Scripting Language
13
4 Assertions In order to improve reliability and self documentation we added assertions to XOT CL. The implemented assertions are modeled after the “design by contract” concept of Bertrand Meyer [15, 16]. In XOT CL assertions can be specified in form of formal and informal preand post-conditions for each method. The conditions are defined as a list of and-combined constraints. The formal conditions have the form of normal T CL conditions, while the informal conditions are defined as comments (specified with a starting “#”). The lists containing the pre- and post-conditions are appended to the method definition (see example below). Since XOT CL offers per-object specialization it is desirable to specify conditions within objects as well (this is different to the concept in [15]). Furthermore there may be conditions which must be valid for the whole class or object at any visible state (that means in every pre- and post-condition). These are called invariants and may be defined with following syntax for class invariants: ClassName classinvar invariantList
or for objects invariants: ObjName invar invariantList
Logically all invariants are appended to the pre- and post-conditions with a logical “and”. All assertions can be introspected. Since assertions are contracts they need not to be tested if one can be sure that the contracts are fulfilled by the partners (see [15]). But for example when a component has changed or a new one is developed the assertions could be checked on demand. For this purpose the check method can be used either to test the pre- or the post-conditions. The syntax is: objName check pre objName check post
;# check pre-conditions ;# check post-conditions
An example is a class for a sensor: Class Sensor Sensor instproc init args { [self] set value 1 } Sensor classinvar {[regexp {[0-9]} $value] == 1} Sensor instproc incrValue {} { [self] instvar value [self] check pre incr value [self] check post } \ {{# pre-condition:} {$value > 0}} \ {{# post-condition:} {$value > 1}} The invariant expresses the condition (using the T CL command regexp), that the value
must be a decimal. The method definition expresses the formal contract between the class and its clients that the method incrValue only gets input-states in which the value of the variable value is positive. If this contract is fulfilled by the client, the class commits itself to supply a post-condition where the variable’s value is larger then 1. Note that the formal conditions are normal T CL conditions. Inside of the method the variable value is brought into the method’s scope, the preconditions and invariants are checked, the task of the method is performed, and then the post-condition is checked. The test is configurable per object. The testing of each kind of condition and the whole testing may be turned on and off. AsserTcl [5] is another approach that introduces assertions into T CL which realizes assertions by four new T CL commands. The advantages of our approach are objectorientation, that the assertions could be found in a familiar place (at the end of the method definition) and that assertions common to a whole class or object could be given in invariants. Furthermore our solution enhances the state idea where assertions must hold in the visible states only (as in [15]).
14
Gustaf Neumann, Uwe Zdun
5 Meta-data To enhance the understandability and the consistency between documentation and program it is useful to have a facility to make the documentation a part of the program. There are several kinds of meta-data which are interesting for a class, e.g. the author, a description, the version, etc. Furthermore in many domains a need for meta-data evolves, like the WWW where web-objects can be described with structured meta-data approaches, like RDF [13]. In XOT CL meta-data can be provided as atomic or structured values (like lists or object names), and can be added or removed at any time. Meta-data is inherited such that metadata defined on a class can be propagated to all instances. Syntactically, meta-data can be specified through the metadata method with its options add and remove. Other arguments for the metadata method are interpreted as meta-data values, which may be set (with given argument) or queried. Introspection of meta-data is implemented through the info method. Below is an example for defining meta-data for a class: Object metadata add {description version} Object metadata description "This is the object \ class. It realizes all common object behavior." Object metadata version "1.0"
Since the meta-data is inherited, all objects get the ability to store information on description and version. For example the agent a1 may store such values: Class Agent Agent a1 a1 metadata description "My testing agent" a1 metadata version "0.1"
Every class and object can store additional meta-data for its own purposes, which are also inherited by all sub-classes (if they are defined on a class). E.g. all agents can store information about the host on which they have been created and the testing agent a1 can store information about the locations it had tested already. Agent metadata add homeURL a1 metadata homeURL "http://nestroy.wi-inf.uni-essen.de/" a1 metadata add testedLocations a1 metadata testedLocations { http://nestroy.wi-inf.uni-essen.de/ http://www.uni-essen.de/ }
After a while the testing agent may have performed all desired tests, so he may decide to stop the testing activity. Therefore, he can turn into another kind of agent and the testing information can be removed: a1 metadata remove testedLocations
Introspection of meta-data is implemented through the info method. The metadataoption informs about the meta-data available on an object: objName info metadata
All meta-data defined on the object and inherited from the class is enlisted by the command above. The values for meta-data may be queried by the metadata method without a value, e.g.: Object metadata version
This will produce the result “1.0”. Meta-data resembles normal instance variables. Beneath the special features, like inheritance, meta-data could be expressed through instance variables solely. But especially in distributed environments, it is important to have such a facility, because the common place and the introspection mechanisms allow different people and, in XOT CL’s case, even other programs, to gain the meta-data without searching for them. Since this is not only a naming convention, but a language construct, the interpreter results in an error if the meta-data is used falsely.
XOT CL , an Object-Oriented Scripting Language
15
6 Per-Object Mix-ins Per-object mix-ins are a novel approach of XOT CL to handle complex data-structures dynamically. It may be referred to as a “light version” of the filters presented in the next section. While the filters work on classes and class hierarchies, the per-object mix-in is applied on a single object. A per-object mix-in is a class which is mixed into the precedence order of an object directly before the object’s class itself is searched.
All methods which are mixed into the execution of the current method, by method chaining or through a per-object mix-in, are called mix-in methods.
6.1 Motivation Per-object mix-ins cover a problem which is not solvable elegantly just by the method chaining, introduced so far. To bring in an addition to a class, the normal XOT CL way is to define a mix-in and chain the methods through next, e.g.: Class Basic Basic instproc someProc {} { # do the basic computations } Class Addition Addition instproc someProc {} { # do the additional computations [self] next }
In order to mix-in the additional functionality in OT CL a new class has to be defined, like: Class Basic+Addition -superclass {Addition Basic}
This is even applicable in a dynamical manner, every object of the class Basic may be changed to class Basic+Addition at arbitrary times, e.g.: Basic basicObj ... basicObj class Basic+Addition
Now we consider a situation with two addition classes. Then following set of classes has to be defined to cover all possible combinations: Class Basic Class Addition1 Class Addition2 Class Basic+Addition1 -superclass {Addition1 Basic} Class Basic+Addition2 -superclass {Addition2 Basic} Class Basic+Addition1+Addition2 \ -superclass {Addition2 Addition1 Basic}
The number of classes needed to cover the combinations of the additions rises exponential. For n additions, 2n ? 1 artificially constructed helper-classes are needed for all combinations of additional mix-in functionality (when the order of the additions matters, all permutations have to be covered, when the additions depend on side-effects, the number of additions with different behavior is unlimited). This demonstrates clearly that the subclass mechanism provides only a poor mechanism for mix-in of orthogonal functionality. Therefore we provide an extension in the form of object mix-in methods, which are added in front of the search precedence of classes.
16
Gustaf Neumann, Uwe Zdun
6.2 Usage of Per-Object Mix-in Methods The mix-ins methods extend the next-path of shadowed methods. Therefore, per-object mix-in methods use the next-primitive to access the next shadowed method. Consider the following example: Class Agent Agent instproc move {x y} { # do the movement } Class InteractiveAgent -superclass Agent # Addition-Classes Class MovementLog MovementLog instproc move {x y} { [self] next } Class MovementTest MovementTest instproc move {x y} { [self] next }
# movement logging
# movement testing
An agent class is defined, which allows agents to move around. Some of the agents may need logging of the movements, some need a testing of the movements, and some both (perhaps only for a while). These functionalities are achieved through the additional classes, which we will apply through per-object mix-ins. Before we can use the per-object mix-ins on a particular object, we must register the mix-ins on it (with the mixin instance method). For example we may create two interactive agents, where one is logged and one is tested: InteractiveAgent i1; InteractiveAgent i2 i1 mixin MovementLog i2 mixin MovementTest
At arbitrary times the mix-ins can be changed dynamically. For example i2’s movements can also be logged: i2 mixin {MovementTest MovementLog}
Figure 4 shows the situation of the object i2 and it’s next-path. The mixin option of the
Object
next Agent
next next MovementTest
MovementLog
InteractiveAgent
next
instance-of i2
next
Fig. 4. Per-Object Mix-ins: Next-Path for the Example
info instance method allows to introspect the per-object mix-ins. It has the syntax: objName info mixin ?class? It returns the list of all mix-ins of the object, if class is not specified, otherwise it returns 1, if class is a mix-in of the object, or 0 if not.
XOT CL , an Object-Oriented Scripting Language
17
7 The Filter as a Means to manage large Program Structures Even though object-orientation orders program structures around data, objects are characterized primarily by their behavior. Object-oriented programming style encourages the access of encapsulated data only through the methods of the object, since this allows data abstractions [25]. A method invocation can be interpreted as a message exchange between the calling and the called object. Therefore, objects are at runtime only traceable through their message exchanges. At this point the filters, which is able to catch and manipulate all incoming and outgoing messages of an object, starts it’s work. The filter is a novel approach to manage large program structures. It is a very general mechanism which can be used in various application areas. E.g. a very powerful language support for design patterns (see Section 7.4 for examples) is easily achievable, but there are also several other domains which are covered, like tracing of program structures, self documentation at run-time, re-interpretation of the running program, etc. A filter is a special instance method that is registered for a class C . Every time an object of class C receives a message, the filter method is invoked automatically.
7.1 Usage of Filters All messages to objects of the filtered class must go through the filter, before they reach their destination object. A simple example would be a sole filter on the class of the object. To define such a filter two steps are necessary. Firstly a filter- instproc has to be defined, then the filter has to be registered. The filter- instproc consists of three parts which are all optional. A filter instproc has the following form: ClassName instproc FilterName args { pre-part [self] next post-part }
When a filter comes to execution at first the actions in the pre-part are processed. Afterwards the filter is free in what it does with the message. Especially it can (a) pass the message, which was perhaps modified in the pre-part, to other filters and finally to the object. It can (b) redirect it to another destination. Or it can (c) decide to handle the message completely. The forward passing of messages is implemented through the next-primitive of XOT CL . After the filter has passed its pre-part, the actual called method is invoked through next (as illustrated in Figure 5). Afterwards, similar to next-calls in OT CL, the execution returns to the point in the filter, where the next-call is located and resumes execution with the actions of the postpart. These may contain arbitrary statements, but especially may take the result of the actual called method (which is returned by the next-call) and modify it. The caller then receives the result of the filter instead of the result of the actual called method. The pre- and postpart may be filled with any ordinary XOT CL-statements. The distinction between the three parts is just a naming convention for explanation purposes. The filter uses the args argument which allows a list of variable length as arguments, since it must filter a lot of different calls, which may have different argument lists. Furthermore, it may pass through arguments to other filters and the preceding filters may change the argument list. Since any instproc may be a filter, a registration of the filter is necessary, in order to tell XOT CL , which instprocs are filters on which classes. The filter instance method is able to handle this task. Similar to the XOT CL-language introduced so far, the filter registration is dynamical at run-time. By choosing one of the options add or remove, the programmer can change the filters registered on a class at arbitrary times. Now a simple example should show the filter’s usage. In the preceding examples we have defined several rooms. Every time a room action occurs it is likely that the graphical
18
Gustaf Neumann, Uwe Zdun
sub-system has to change something on the output of that particular room. Therefore, at first we need a facility to be informed every time an action on a room happens. This is quite easily done using filters: Class Room Room r1; Room r2;
# just two test objects
Room instproc observationFilter args { puts "now a room action begins" set result [[self] next] puts "now a room action ends -- Result: $result" return $result } Room filter add observationFilter
Now every action performed on room-objects is noticed with a pre- and a post-message to the standard output stream. We return the result of the actual called method, since we don’t want to change the program behavior at all. E.g. we can set an instance variable on both of the two room objects: r1 set name "room 1" r2 set name "room 2"
The output would be: now now now now
a a a a
room room room room
action action action action
begins ends -- Result: room 1 begins ends -- Result: room 2
All classes may have more than one filter. In fact they may have a whole filter chain, where the filters are cascaded through next (see Figure 5). The next method is responsible for
message (c) message (b)
}
filter-chain Class A
Class A
message (a) o0
Filter_3 Filter_2 Filter_1
pre-part
call
post-part
result
a1
Fig. 5. Cascaded Message Filtering
the forwarding of messages to the remaining filters in the chain one by one till all pre-parts are executed. Afterwards the actual method is executed and then the post-parts come to turn. If one next-call is omitted the chain ends in this filter method. As an example for an additional filter we may register a filter, which just counts the calls to rooms. Room set callCounter 0 ;# set class variable Room instproc counterFilter args { [self class] instvar callCounter incr callCounter puts "the call number $callCounter to a room object"
XOT CL , an Object-Oriented Scripting Language
19
[self] next } Room filter add counterFilter
Filters are invoked in registration order. The order may be changed by rem oving them and adding them in new order. Filters are inherited by sub-classes. Remember the example class graph in Figure 3. An OvalOffice was derived from the Room class. Without a change to the program each OvalOffice object automatically produces the same filter output as rooms.
Class Object 3 4
pre-part Class A a1
2 5
Class B
1
b1
b2
Filter Chain of Class B
6
b1 set x 10 o1
post-part
Fig. 6. Filter Inheritance
Filter chains can also be combined through (multiple) inheritance using the next method. When the filter chain of the object’s class is passed, the filter chains of the superclasses are invoked using the same precedence order as for inheritance (see Figure 6). Since on the sub-class there may also be a another filter chain, without sophisticated computing in the pre- and post-parts one can produce easily a powerful tracing facility. E.g. if we want to distinguish an OvalOffice from other rooms we may want to add a filter solely for rooms of the type OvalOffice: Class OvalOffice -superclass Room OvalOffice o1; # test object OvalOffice instproc observationFilter args { puts "actions in an oval office" [self] next } OvalOffice filter add observationFilter A simple call to the o1 object, like: o1 set location "Washington"
produces the following output: actions in an oval office now a room action begins the call number 3 to a room object now a room action ends -- Result: Washington
As seen already filter registrations can be added dynamically at runtime. But they may also be removed. Perhaps the counting on rooms should stop after a while, then a simple call of the filter method is sufficient: Room filter remove counterFilter
20
Gustaf Neumann, Uwe Zdun
7.2 Introspection of Filters Since filters are normal XOT CL instprocs they can access all XOT CL introspection abilities. In instprocs all recent information is accessible within their scope. But the filter is a mechanism, which covers more then this sole scope. The meaningful usage of its metaprogramming abilities often requires to go further and to get information from the caller’s and the callee’s scope (e.g for delegation decisions in the filter). Therefore, we introduced rich introspection abilities for the filter (see Table 5).
objName info calledproc
Returns the name of the proc which was invoked in the original call.
objName info callingclass
Returns the name of the class from which the filtered call was invoked (if one exists, otherwise an empty string).
objName info callingproc
Returns the name of the proc from which the filtered call was invoked (if one exists, otherwise an empty string).
objName info callingobject
Returns the name of the object from which the filtered call was invoked (if one exists, otherwise an empty string).
objName info regclass
Returns the name of the class on which the filter is registered. Since the filter may be registered on other classes then the class on which it is defined, this may vary from self class.
Table 5. The additional Introspection Mechanisms for Filters
Note, that three options with the prefix calling represent the values of self, self proc, and self class at the point where the original call was invoked. In the following section we will show a simple program in which all of the info-options have different values, after we have discussed the similar looking trace program. 7.3 Examples A simple Trace Program The trace example primarily demonstrates the inheritance of filter chains. Since all classes inherit from Object, a filter on this class is applied on all messages to objects. The Trace class encapsulates the filter and the methods to format the output messages etc.: Class Trace Trace proc show {call name method arguments} { ... } ...
The actual filter method (see below) displays the calls and exits of methods with an according message. The calls are supplied with the arguments, the exit traces contain the result values. We have to avoid the tracing of the trace methods explicitly. Trace instproc traceFilter args { # don’t trace the Trace object if {[self] == "::Trace"} {return [[self] next]} # trace in user classes set method [[self] info calledproc] Trace show CALL [self] $method $args
XOT CL , an Object-Oriented Scripting Language
21
set result [[self] next] Trace show EXIT [self] $method ($result) return $result } Object filter add Trace::traceFilter
When the trace filter is registered for the class Object, all invocations of methods in the whole system are traced. In general, it is possible to restrict the trace to instances of certain classes, or to produce trace output for only certain methods. This requires registration methods and a more sophisticated implementation of the filter method. Information Example Now we discuss a simple example that shows that all filter introspection options may have different values: Class InfoTrace InfoTrace instproc infoTraceFilter args { puts "SELF: [self]" puts "SELF PROC: [self proc]" puts "SELF CLASS: [self class]" puts "INFO CLASS: [[self] info class]" puts "CALLED PROC: [[self] info calledproc]" puts "CALLING PROC: [[self] info callingproc]" puts "CALLING OBJECT: [[self] info callingobject]" puts "CALLING CLASS: [[self] info callingclass]" puts "REGISTRATION CLASS: [[self] info regclass]" [self] next } Class CallingObjectsClass CallingObjectsClass callingObject Class FilterRegClass Class FilteredObjectsClass -superclass FilterRegClass FilteredObjectsClass filteredObject CallingObjectsClass instproc callingProc {} { filteredObject set someVar 0 } FilterRegClass filter add InfoTrace::infoTraceFilter
The invocation of callingObject callingProc produces the following output: SELF: ::filteredObject SELF PROC: infoTraceFilter SELF CLASS: ::InfoTrace INFO CLASS: ::FilteredObjectsClass CALLED PROC: set CALLING PROC: callingProc CALLING OBJECT: ::callingObject CALLING CLASS: ::CallingObjectsClass REGISTRATION CLASS: ::FilterRegClass The filter reports for self the value filteredObject, since this is the object on which the set call is invoked; infoTraceFilter is the method of the filter, and therefore the actual proc, while the actual class is InfoTrace, the filter’s class. The class of the actual object is FilteredObjectsClass. The called procedure is set. While the program stays in a XOT CL-instproc all callinginfo-options are set, the calling procedure is callingProc, the calling class is the class, where the method is defined (namely CallingObjectsClass), and the object from which the call invoked is callingObject. Since the filter’s registration class differs from the
class, where it is defined, the corresponding information is still missing (in this example FilterRegClass).
22
Gustaf Neumann, Uwe Zdun
7.4 Language Support for Design Pattern through Filters One driving motivation for the design of the filter approach was the language support for design patterns. In [18] we concentrate more on this aspect, while in this paper the exact description of the language element “filter” was in the focus. But nevertheless we want to present a brief overview of our ideas for language support of design patterns and one solution for a specific pattern: the composite pattern. Refer to [18] or [27], to get more impressions of pattern solutions in XOT CL or a more sophisticated discussion of the language support ideas. Also a description and comparison to basic works, like [8, 4] and to related works, like [24, 1, 2, 10, 6], is provided there. Design Patterns in Scripting Languages The complexity of many large applications is caused by the combination of numerous, often independently developed components which have to work in concert. Typically a high number of classes is involved with different kinds of non-trivial relationships like inheritance, associations and aggregations. Design patterns provide abstractions over reusable designs, that can typically be found in the “hot spots” [22] of software architectures. Patterns are designed to manage complexity in the design process by merging interdependent program structures into one (abstract) design entity. Design patters are increasingly more often considered as reusable solutions for general problems. Specialized instances of the design patterns can be used in a diversity of applications. Soukup defines a pattern as following [24]: “A pattern describes a situation in which several classes cooperate on a certain task and form a specific organization and communication pattern.”
Design patterns are collected in pattern catalogs [8, 4]. Typically these catalogs contain general patterns, but there are also catalogs which collect domain specific patterns. Language Support for Design Patterns Most authors present design patterns as guidelines for the design, which have to be recoded for every concrete implementation according to the class diagram and implementation example from the catalog. Our idea is to give the programmer the means to code a pattern once in an abstract way (e.g. for a pattern-library) and reuse it later in a specialized manner. This approach has the advantage that patterns can be used as (abstract) entities in the design process as well as entities in the implementation. There are only a few efforts in the direction of language support for design patterns so far. We believe that one reason for this lack of support is due to the targeted languages. Conventional object-oriented programming languages like C++ offer no support for reproduction of larger structures than classes. But besides the consistency between design and implementation phase (and between the resulting structures) there are more reasons [2] why language support for design patterns should be improved: – Traceability: The pattern is scattered over the objects and therefore hard to locate and to trace in an implementation. – Self-Problem: The reference to the message-receiving object gets lost through message-forwarding. – Reusability: The implementation of the pattern must be recoded for every use. – Implementation Overhead: The pattern implementation requires several methods with trivial behavior. The Composite Pattern A pattern from [8] is the composite pattern, shown in Figure 7. The composite pattern helps to arrange objects in hierarchies with a unique interface type, called component. The objects are arranged in trees with two kinds of components: leaves and composites. Composites can hold other components. Therefore, recursive structures could be build.
XOT CL , an Object-Oriented Scripting Language Client
23
Component Operation Add Remove GetChild
Leaf Operation
Composite
children
Operation Add Remove GetChild
forall g in children g.Operation();
Fig. 7. The Composite Pattern [8]
In the implementation of [8] are several disadvantages, due to the used language C++. The composite pattern structure contains object aggregation, though C++ has no such language feature, the implementation lacks of flexible mechanisms to handle and introspect the aggregation and an implementation overhead resulting out of the necessity to store a list of the aggregates. Furthermore, the absence of language support for patterns structures leads to a mixing of application and pattern, what reduces reusability, because the pattern is scattered over the orthogonal application structures. The pattern is no abstract entity, therefore, it is hard to specialize and to reuse it. Also it is not easy to find it in source code, if it is not well commented, and both description in the pattern classes and the runtime object structure are hard to introspect and not traceable. The solution in XOT CL does not suffer of these disadvantages. We will implement the generic pattern tasks, which are scattered in Figure 7 over the classes, into one meta-class Composite. The resulting abstract pattern is therefore instantiable into several classes: Class Composite -superclass Class Composite instproc addOperations args {...} Composite instproc removeOperations args {...}
Operation calls that are forwarded to the objects aggregated in the composite pattern are added and removed by addOperations and removeOperations. These registered operations are stored in an associative array, which is a class variable for all composites. All generic pattern tasks will be performed by a filter that handles the forwarding fo the methods to the components of a composite: Composite instproc compositeFilter args { [[self] info class] instvar operations set r [[self] info calledproc] if {[info exists operations($r)]} { foreach object [[self] info children] { eval [self]::$object $r $args } } return [[self] next] }
The operations array is stored as a class variable on the class of the object, which is returned by info class. Note, that self class would return the current class, which is the class of the filter. info exists checks whether the request is an existing entry of the operations array or not. If the request is found, the message is forwarded to all children objects. Though children may be composites, this mechanism functions recursively on the entire structure, until the leaves are reached. In order to register the filter on a new composite class automatically, we use filter add in the constructor of the meta-class: Composite instproc init {args} {
24
Gustaf Neumann, Uwe Zdun [self] next [self] filter add Composite::compositeFilter }
The whole pattern is put into one unit of the program, which becomes instantiable through definition as a meta-class. The meta-class handles all semantics of the pattern. For the sake of simplicity we have not introduced component types (what allows to aggregate objects that are not components) or composite types (what allows to aggregate composite pattern structures recursively). As a sample application, we will construct a composite graphic: Class Graphic Graphic instproc draw {} {...}
Now we define a composite Picture with two leaves (as normal classes): Line and Rectangle. Composite Picture -superclass Graphic Class Line -superclass Graphic Class Rectangle -superclass Graphic
aPicture bPicture
aLine
aLine
aRect
cPicture
dPicture
aRect
cLine
Fig. 8. The Composite Object Structure
The following commands create the structure displayed in Figure 8: Picture aPicture Picture aPicture::bPicture Line aPicture::aLine Rectangle aPicture::aRect Line aPicture::bPicture::aLine Rectangle aPicture::bPicture::aRect Picture aPicture::bPicture::cPicture Picture aPicture::bPicture::dPicture Line aPicture::bPicture::cPicture::cLine
After registration of the draw operation with: Picture addOperations draw
an invocation of the draw method on a complex object, like: aPicture draw
draws the whole hierarchy.
8 Conclusion This paper introduces XOT CL which is an extension of the OT CL language. We discuss the new language features of XOT CL and the few incompatibilities with OT CL. Since the language is presented in the paper by examples, the paper aims at the programmer. But nevertheless we implicitly introduced the underlying principles of programming, like using
XOT CL , an Object-Oriented Scripting Language
25
dynamic typing, flexible glueing of preexisting components, using component frameworks etc., that can lead towards a higher productivity and software reuse. XOT CL gives the programmer powerful means to manage complexity and introduces more functionalities to improve the flexibility of the language. One interesting application area for the new language constructs are design patterns, which are discussed in more detail in [18]. Our new language constructs, which would have been difficult to apply in other then scripting languages (in the presented dynamic and introspective fashion), are an attempt to combine object-orientation and scripting languages in way that the programmer can profit of the benefits of both and gets powerful and easy-to-use means to go beyond that. Currently we are working on a release version, which will be downloadable at the time of the conference. Furthermore, we are working on a design pattern library (with the patterns from [8]) and are testing applications of the new language constructs (e.g. in the extensible web browser Cineast [11]). We plan to investigate dependencies to the mobile and distributed computing.
References
1. J. Bosch: Design Patterns as Language Constructs, http://bilbo.ide.hk-r.se:8080/ bosch/, 1996. 2. J. Bosch: Design Patterns and Frameworks: On the Issue of Language Support, LSDF’97, also available as http://bilbo.ide.hk-r.se:8080/ bosch/, 1997. 3. D.G. Bobrow, L.G. DeMichiel, R.P. Gabriel, S.E. Keene, G. Kiczales, D.A. Moon: Common Lisp Object System. In: Common Lisp the Language, 2nd Edition, http://info.cs.pub.ro/onl/lisp/clm/ node260.html, 1989. 4. F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal: Pattern-oriented Software Architecture – A System of Patterns, J. Wiley and Sons Ltd, 1996. 5. J.E. Cook: Assertions for the Tcl Language, Proceedings of the Fifth Annual Tcl/Tk Workshop 1997, Boston, Massachusetts, 1997. 6. S. Ducasse: Message Passing Abstractions as Elementary Bricks for Design Pattern Implementation, LSDF’97, also available as http://bilbo.ide.hk-r.se:8080/ bosch/lsdf/, 1997. 7. J. L. Fontain: Simple Tcl Only Object Oriented Programming, http://www.mygale.org/04/ jfontain/, 1998. 8. E. Gamma, R. Helm, R. Johnson, J. Vlissides: Design Patterns: Elements of Reusable ObjectOriented Software, Addison-Wesley, 1994. 9. L. Hatton: Does OO Sync with How We Think?, in: IEEE Software, May/June 1998. 10. G. Hedin: Language Support for Design Patterns using Attribute Extension, LSDF’97, also available as http://bilbo.ide.hk-r.se:8080/ bosch/lsdf/, 1997. 11. E. K¨oppen, G. Neumann, S. Nusser: Cineast – An Extensible Web Browser, Proceedings of the WebNet 1997 World Conference on WWW, Internet and Intranet, Toronto, Canada, November 1997. 12. G.E. Krasner, S.T. Pope: A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80, Journal of Object Oriented Programming, Vol. 1 (3), pp. 26-49, 1988. 13. O. Lassila, R.R. Swick: Resource Description Framework (RDF): Model and Syntax, http://www.w3.org/TR/WD-rdf-syntax/, 1998. 14. M.J. McLennan: The New [incr Tcl]: Objects, Mega-Widgets, Namespaces and More, Proceedings of the Tcl/Tk Workshop ’95, Toronto, July 1995. 15. B. Meyer: Object-Oriented Software Construction - Second Edition, Prentice Hall, 1997. 16. B. Meyer: Building bug-free O-O Software: An Introduction to Design by Contract, http://eiffel.com/doc/manuals/technology/contract/index.html, 1998. 17. G. Neumann, S. Nusser: Wafe – An X Toolkit Based Frontend for Application Programs in Various Programming Languages, USENIX Winter 1993 Technical Conference, San Diego, California, January 1993. 18. G. Neumann, U. Zdun: Filters as a Language Support for Design Patterns in Object-Oriented Scripting Languages, submitted for publication, 1998. 19. J. K. Ousterhout: Tcl: An embeddable Command Language, Proceedings of the 1990 Winter USENIX Conference, 1990. 20. J. Ousterhout: Scripting: Higher Level Programming for the 21st Century, in: IEEE Computer, Vol. 31, No. 3, also available as http://www.scriptics.com/people/john.ousterhout/scripting.html, March 1998.
26
Gustaf Neumann, Uwe Zdun
21. J. K. Ousterhout: Tcl and Tk, Addison-Wesley, 1994. 22. W. Pree: Design Patterns for Object-Oriented Software Development, Addison-Wesley, 1995. 23. J. Smith, D. Smith: Database Abstractions: Aggregation and Generalization, ACM Transactions on Database Systems, 2:2, June 1977. 24. J. Soukup: Implementing Patterns, in: J.O. Coplien, D.C. Schmidt (Eds.), Pattern Languages of Program Design, Addison-Wesley 1995, pp 395-412, also available as http://www. codefarms.com/publications/papers/patterns.html, 1995. 25. P. Wegner: Learning the Language, in: Byte, Vol. 14, No. 3, pp. 245 – 253, March 1989. 26. D. Wetherall, C.J. Lindblad: Extending Tcl for Dynamic Object-Oriented Programming, Proceedings of the Tcl/Tk Workshop ’95, Toronto, July 1995. 27. U. Zdun: Entwicklung und Implementierung von Ans¨atzen, wie Entwurfsmustern, Namenr¨aumen und Zusicherungen, zur Entwicklung von komplexen Systemen in einer objektorientierten Skriptsprache, Diplomarbeit, Universit¨at Gesamthochschule Essen, 1998.