Software Reuse in an Object Oriented Framework: Distinguishing Types from Implementations and Objects from Attributes J. Leslie Keedy1, K. Espenlaub1, G. Menger1, A. Schmolitzky2, M. Evered3 1Department
of Computer Structures, University of Ulm D-89069 Ulm, Germany {keedy, gisela, espenlaub}@informatik.uni-ulm.de 2Peninsula School of Computing and Information Technology, Monash University Victoria 3199, Australia
[email protected] 3School of Mathematical and Computer Sciences, University of New England Armidale, N.S.W. 2351, Australia
[email protected]
Abstract. Almost no object oriented programming languages offer distinct language constructs for the definition of types and their implementations; instead these are united into a single class concept. Similarly object oriented programming languages do not normally distinguish between object types, which may be independently instantiated, and attribute types, which may not. The paper shows how these distinctions can be used to develop both a specialized and a generalized bracket technique, and how the ideas lead to interesting possibilities for reusing code in a flexible and modular way.
1
Introduction
The basic idea of object-oriented programming – that a program is decomposed into objects which are defined in terms of classes, the methods of which determine the behavior of the objects which are instances of a particular class – can contribute in a general way to the reuse of software. This idea, in the hands of an experienced designer, leads to modular software which can correspond to "real" objects in the "real world", and these often find an application in many different situations. More specifically the idea of inheritance, which is often regarded as the distinguishing characteristic of object oriented programming, is considered by many to be useful primarily because it allows the code ("methods") which has been developed to support a class of objects to be reused in the implementation of subclasses. In this paper the idea of code reuse in object-oriented programming is taken two steps further. First, a distinction is made between the definition of classes (as the central construct in most object oriented languages) on the one hand, and of types and implementations on the other hand. Conventionally, a "class" unites the concept of type and implementation in such a way that these are effectively inseparable. This conventional approach has many disadvantages. For example a class cannot have multiple
implementations without affecting its status as a type. By separating the concepts of an object type (i.e. a specification of the interface and behavior of a class of objects) and an object implementation (i.e. a code module or modules which actually implement the specification) and treating these as orthogonal concepts, it becomes possible to envisage that a type may have more than one implementation, even in a single program. In fact this distinction leads to a novel view [1] (for the object oriented world) of software reuse, in that for example one implementation of a type can (but need not) be reused in a different implementation of the same type. A further important effect of this distinction is that inheritance in the sense of code reuse (subclassing) can be clearly distinguished from inheritance in the sense of type specialization (subtyping), cf. e.g. [9, 19, 20] and this for example leads to clearer program designs. Second, a distinction is drawn between objects and attributes. The basic idea behind this distinction was first explained in [16]. In the present paper some aspects of the idea are described and their relevance to the reuse of software is highlighted. Each of these extensions to object oriented programming provides a (different) basis for introducing the idea of bracket routines, i.e., routines which are designed to bracket another routine or routines. In the case of the type/implementation distinction the bracket routines can be "specialized", whereas the object/attribute distinction provides a basis for "generalized" bracket routines. Each has its own uses, and both can add considerably to the range of potential reuses of software. The advantages to be gained from these two ideas cannot be realized using existing programming languages. For this and other reasons an experimental programming language L1 is being developed at the University of Ulm. This has its roots in object orientation and in procedural languages in the style of Pascal and its successors. More recently the ideas based on the distinction between type and implementation definitions were extended and incorporated into the programming language Tau [25], which is an experimental modification of the Java language [14]. The notation used in the examples is based on L1. In general this notation is selfexplanatory, but footnotes are used where appropriate to explain unusual ideas. Section 2 examines the idea of separating types from implementations in relation to the reuse of code and shows how this separation can lead to a technique for introducing specialized brackets. In section 3 the separation of object types from attribute types is motivated in terms of code reuse, and this leads to a technique for implementing more generalized bracket code. The basic differences between specialized and generalized brackets are outlined in section 4. Then in section 5 the advantages of both techniques for code reuse are summarized. Section 6 refers to related work and section 7 concludes the paper.
2
Separating Definitions of Types and Implementations
In this section we consider the consequences for code reuse of separating definitions of types from definitions of implementations.
2.1
Achieving Standard Code Reuse
The definition of an object type in L1 (known via the keyword objtype) can be viewed as a semi-formal specification of a class of objects, consisting of the signatures of the methods associated with the object class and possibly some natural language explanations. These signatures may include pre and post conditions and definitions of exceptions which the methods might raise, but such features are ignored in the examples in this paper for the sake of simplicity. We begin with a simple example of an object type book: objtype book op set_title (in title: string) enq get_title: string op set_author (in author:string) enq get_author: string op set_isbn (in isbn: isbn_num) enq get_isbn: isbn_num constr new end book
The keyword constr introduces a constructor. The keyword op introduces a routine known as an operation, which can modify the state of an instance of the class; the keyword enq introduces an enquiry, which can return information about the state of an object, but may not modify the state. L1 conveniently allows a pair of routines for setting and getting the state of a component to be defined together as a var. Thus the above definition is equivalent to: objtype book var title: string author: string isbn: isbn_num constr new end book
However this is merely syntactic sugar; it does not imply that such a component is actually represented as a variable in an implementation of the type. An object type can inherit from another object type, as indicated by the keyword isa, e.g. objtype loanable_book isa book var currently_loaned: bool due_date: date borrower: person enq days_overdue: int enq date_returned: date op put_on_loan (in b: person; d: date) op return_from_loan (in d: date) end loanable_book
An implementation is normally explicitly associated with a type definition, as follows: impl book_impl_1 for book .. data and code for the methods .. end book_impl_1
In the case of a simple type such as book, which contains only var definitions and a simple constr method, the compiler can create an implementation automatically, but if the programmer chooses he can provide an explicit implementation or
implementations (for example to provide consistency checking in the operation set_isbn). An implementation may reuse the code of another implementation. This technique can be used to achieve reuse for inheritance, as in conventional object orientation, e.g. impl loan_book_1 for loanable_book reuse book_impl_1 .. data and code for the methods not inherited from book .. end loan_book_1
The code of a supertype need not be reused in this way, but when it is, this does not exclude the possibility that some of the methods from the reused module might be overridden in the new implementation. In an inherited routine which is overridden (redefined) it is sometimes convenient to reuse the original code. This is possible in some object oriented languages using a technique such as super as found in Smalltalk-80 [13]. For this purpose L1 uses a "hat" symbol, as follows: impl loan_book_1 for loanable_book reuse book_impl_1 op set_isbn (in isbn: isbn_num) begin .. new code .. ^set_isbn .. new code end set_isbn .. data and code for other methods .. end loan_book_1
2.2
Multiple Implementations of a Type
Because the concepts of type and implementation are united in most object oriented languages into a single class construct a type may have only one implementation and the reuse of code is restricted to the cases described in section 2.1. However in L1 and Tau a type may have more than one implementation (and these may be used concurrently for different objects of the same type in a single program1). This creates new possibilities for code reuse. For example an implementation of a type can reuse code from another implementation of the same type, e.g. impl book_impl_2 for book reuse book_impl_1 .. data and code for some methods .. end book_impl_2
In this case the second implementation reuses the code of the first implementation for all the methods which are not explicitly overridden. The use of the hat symbol in this context provides a natural way of bracketing code in such a manner that it is not linked to the type inheritance hierarchy, e.g.
1
The binding of implementations to objects is determined by a combination of defaults and pragmas.
impl book_impl_2 for book reuse book_impl_1 op set_isbn (in isbn: isbn_num) begin .. new code .. ^set_isbn .. new code end set_isbn .. data and code for some methods .. end book_impl_2
Notice that such "bracket" implementations are specialized, in the sense that the bracketing part and the bracketed part both have access to the parameters of the routine. 2.3
Type Independent Reuse
So far the examples have illustrated how code can be reused in the context of a type hierarchy (section 2.1) or a single type (section 2.2). In fact both in L1 and in Tau the concepts of type and of implementation are kept fully orthogonal, so that it is also possible for an implementation to be reused in an implementation of a completely independent type, or even in a case where the subtyping and subclassing relations run contrary to each other. An example of the second case was described in [2]. The scenario at the type level involves a double ended queue inheriting from a normal queue, e.g. objtype queue op put_at_back (in e: element) op get_from_front (out e: element) constr new end objtype d_e_queue isa queue op put_at_front (in e: element) op get_from_back (out e: element) end
Given an existing implementation d_e_q_1 of the type d_e_queue, it is attractive to reuse the code of this to implement the type queue. In L1 and Tau this is simple to achieve: impl d_e_q_1 op put_at_front (in e: element) begin ... end op put_at_back (in e: element) begin ... end op get_from_front (out e: element) begin ... end op get_from_back (out e: element) begin ... end constr new begin ... end end d_e_q_1 impl q1 for queue reuse d_e_q_1 end
This is only possible because the separation of types from implementations is kept fully orthogonal. In section 3.3 we shall encounter an example of code reuse involving an implementation of two independent types.
3
Attribute Types and Object Types
We now turn to the second key feature which allows the reuse of code to be increased in an object oriented context: the distinction between object types and attribute types. 3.1
The Basic Concept
The relationship between attribute types and object types is analogous to the relationship in natural language between adjectives and nouns. An object generally corresponds to a noun and an attribute to an adjective. Consider a (motor) car: in the English language the word "car" is a noun and in an object-oriented language a (particular) car can be viewed as an object. Similarly a "book" is a noun in English and potentially an object in a program. In natural languages nouns can be qualified by adjectives. Some examples of adjectives in English are "loanable" and "catalogued". The fact that nouns can be qualified by adjectives gives natural languages an enormous flexibility, because (a) the same adjective can be used to qualify many nouns, e.g. a "loanable book" or a "loanable car" and (b) a noun can be qualified by more than one adjective, e.g. a "catalogued, loanable book". Often we use set expressions for an association between a noun and one or more adjectives, e.g. we usually call a "loanable car" a "hire car" or "rental car" and a "catalogued, loanable book" a "library book". As we saw in section 2, types in L1 can be defined corresponding to (types of) objects, introduced by the keyword objtype. Types corresponding to (types of) attributes are introduced by the keyword attrtype, e.g. attrtype loanable var currently_loaned: bool due_date: date borrower: person enq days_overdue: int enq date_returned: date op put_on_loan (in b: person; d: date) op return_from_loan (in d: date) end loanable
Notice that this definition is almost identical to that which appears in section 2.1 in the definition of the objtype loanable_book. The key difference is that it has been separated from the type book, and can now be used in a more general way. This results in an alternative way of defining loanable_book as follows: objtype loanable_book isa loanable book end loanable_book
Here a new object type is defined by inheriting from an object type and from an attribute type. It is possible to add new methods when defining a type in this way, e.g. objtype library_book var dewey_number: section_name: shelf_number: end library_book
isa loanable book string string int
One of the main advantages of attribute types is that the same attribute type can easily be combined with different object types. For example, given a further object type car:
objtype car var registration_number: string type: string number_of_seats: int constr new (in reg_num: string car_type: string seat_count: int) end car
it is now easy to define a hire car: objtype hire_car isa loanable car end hire_car
Notice that this not only enhances the modularity of programs; it is also a powerful enhancement of polymorphism, because for example all loanable objects can be passed to a routine, e.g. op send_overdue_notice (in x: loanable) begin if x.days_overdue > 21 then .. endif end
Modularity, generality and polymorphism all favor the use of attribute types rather than conventional inheritance by extension. The definition of loanable_book in section 2.1 is much less flexible than the same type as defined in this section. In fact these arguments suggest that it might also have been advisable to introduce a further attribute type catalogued, rather than add new methods when defining the type library_book, as follows: attrtype catalogued var dewey_number: string section_name: string shelf_number: int end catalogued
and then objtype library_book isa catalogued, loanable book end library_book
In this way these methods can be associated with other catalogued items available in a library such as compact disks, sheet music, theses, etc. This approach is advantageous when compared with languages which provide only single inheritance, because in such languages the methods of generalized attributes cannot be separately defined and therefore cannot be easily combined with quite different object types. For example the methods of loanable have to be separately defined to allow hire_car to inherit from car and to allow library_book to inherit from book. Furthermore it is then impossible to use loanable polymorphically. 3.2
Rules for Attribute Types
Attribute types can be viewed as a special kind of multiple inheritance which is easy to understand and to use. Although languages which support multiple inheritance can achieve a similar effect by defining attributes as objects, this leads to other well known problems. How does L1 avoid the usual problems of multiple inheritance? To understand this we consider the rules which distinguish attribute types from object types.
First, object types must, but attributes may not, include a constructor; consequently only object types can be instantiated. Second, an object type can inherit from at most one object type and from zero or more attribute types. Third, both attribute types and object types may have several implementations, but only implementations of attribute types may include bracket routines. The latter are discussed in the next section. One of the problems which occurs in relation to multiple inheritance is name clashes. L1 solves the problem of the same names occurring in different types by requiring a method name to be qualified by its type name in ambiguous cases. It avoids the "diamond" name clash problem (which occurs when a new type inherits more than once from a supertype) by not allowing an object type to inherit from more than one object type. Another standard problem is with constructors: in L1 an object type can inherit from only one object type, and attribute types do not have constructors. It is interesting that Java distinguishes between classes (which as in other objectoriented languages combine type and implementation definitions) and interfaces. The latter are effectively type definitions, but although these can be implemented in classes, the code of such classes cannot be multiply inherited by other classes. Thus Java interfaces can be reused in an ‘adjectival’ manner, but with the disadvantage that only the type but not the code can be multiply inherited – and therefore the code not easily reused. Java interfaces in fact reinforce the usefulness of the idea of attribute types, in that they are frequently used to represent adjectives such as "cloneable", "runnable", "synchronized" and the like. It is therefore interesting to consider why for example Java interfaces may not have separate implementations which can easily be reused in a general way. The answer appears to be that the available language techniques do not readily provide a mechanism which allows the code of attributes to be kept separate from the code of the classes which they qualify. For example, how can the code of an attribute such as "synchronized" be separated from the code of the classes which it qualifies? 3.3
Bracket Routines for Attributes
One possible approach to this question has been called "aspect oriented programming" [17], which proposes the use of "aspect weavers" to thread different code together; however, there is no universal mechanism called an aspect weaver. L1 also does not pretend to provide a universal aspect weaver capable of solving all potential problems which can arise when "adjectival" attributes are kept orthogonal to object classes. However it does provide a mechanism, generalized bracket routines, referred to in the rest of this section simply as "bracket routines", which can be effectively used in many situations. These are quite different from the specialized bracket routines described in section 2. Bracket routines in L1 can be included in an implementation of an attribute type (but not an object type). The basic idea is that in addition to the normal visible methods associated with an attribute, additional routines may be provided which are implicitly invoked when a client calls a method of the object which is qualified by the attribute. Such routines may include a statement body, which indicates the point(s) at which the explicitly invoked object method is actually invoked. When this method
exits, control is first returned to the bracket routine. The idea can be simply illustrated in the form of an attribute type mutually_exclusive and an implementation of it. attrtype mutually_exclusive end mutually_exclusive
Notice that this type definition is unusual in that it has no explicit methods. impl mutex for mutually_exclusive var2 database: semaphore bracket op, enq -- defines the bracket code for operations begin -- and enquiries of synchronized objects. database.P -- claims exclusion. body -- the body of the object's op or enq. database.V -- releases exclusion. end bracket constr begin body -- the body of the object's constructor. database:= 1 -- initialization of the exclusion semaphore. end end mutex
L1 uses the distinction between the different method categories op, enq and constr to determine which object methods3 are bracketed by which bracket routines. This allows a further attribute read_write_synchronized to be defined and implemented as follows: attrtype read_write_synchronized end read_write_synchronized impl Courtois_et_al for read_write_synchronized reuse mutex var reader_sem: semaphore read_count: int bracket enq -- (re)defines the bracket code for enquiries of begin -- synchronized objects reader_sem.P read_count:= read_count + 1 if read_count = 1 then database.P endif reader_sem.V body reader_sem.P read_count:= read_count - 1 if read_count = 0 then database.V endif reader_sem.V end bracket constr -- (re)defines the constructor bracket code begin body -- the body of the object's constructor read_count:= 0 reader_sem:= 1 database:= 1 end end Courtois_et_al 2
3
This defines an instance field in an implementation as distinct from a pair of methods from a type definition. As was indicated in Section 2.1, a var in a type definition is viewed as an op and an enq, and is treated as such for bracketing purposes.
This solution is based on [6]. It illustrates how an implementation of a type can reuse an implementation from the same or (in this case) a different type4. (In this example the code of bracket op is reused but the implementation of bracket enq and bracket constr are redefined. Explicit methods of an object or attribute can be similarly reused or redefined.) We now look at an example of an attribute type which has both its own independent methods and bracket routines which are invoked in association with calls to the methods of qualified objects. attrtype modification_expiring op set_expiry_date (in ex_date: date) enq get_expiry_date: date end modification_expiring
The intention here is to define an attribute type which does not allow its qualified objects to be modified after a defined expiry date (which can be changed). An implementation: impl no_mod for modification_expiring var expiry_date: date op set_expiry_date (in ex_date: date) begin expiry_date:= ex_date end enq get_ex_date (out ex_date: date) begin ex_date:= expiry_date end bracket op begin if system.today • expiry_date then body endif end bracket constr begin body expiry_date:= system.today end end no_mod
As in all examples in this paper error handling has been ignored; in a real system it would possibly be appropriate for example to raise an exception in an else clause of the conditional statement. From this example we see that bracket routines have the potential to restrict calls to a qualified object, either by omitting a body statement, or as in this case by including it in a conditional statement. This opens up many possibilities for using brackets as a protection mechanism.
4
Generalized and Specialized Brackets
Although L1 is not the first object-oriented language to include a bracket mechanism, it is the first to support both generalized and specialized bracket mechanisms. 4
It can be argued that code reuse which gives access to the internal variables of another module is unattractive, as it violates the information-hiding principle [27] and hinders verification reuse [28].
Generalized bracket routines are clearly suitable for applications such as synchronization, protection, monitoring, etc. where the bracket routine(s) can be programmed entirely independently of the objects which they qualify. This means for example that they have no access to the parameters of the methods which they bracket. On the other hand specialized bracket routines are provided in implementations of specific types. They can therefore access the parameters of the bracketed method and can thus be useful in quite different circumstances from generalized bracket routines. For example specialized brackets can be used in a transaction processing system to write the parameters of each transaction to a log file before the transaction is processed in the body of the routine. They can be used to carry out consistency checks, to implement pre and post routines, etc. This technique also encourages the separation of aspects and provides a method for "weaving" them together. Notice that with generalized brackets it is generally the brackets themselves which are reusable, whereas in the case of specialized brackets it is the bracketed code which is reused.
5
Levels of Software Reuse
The driving motivation for the L1 design was not to make code more reusable as such, but rather to define clean orthogonal structures which simplify modeling of the real world and thus simplify program design, implementation and maintenance. But inevitably this leads to better modularity and to more possibilities for the reuse of software in an object-oriented environment. We now review some of the different possibilities for reuse of code. (i) The first major distinction between L1 and almost all other object-oriented languages is the separation of types from implementations. This means that types can easily be reused independently of implementations. This is not unimportant, because type definitions (together with appropriate comments describing the semantic intention) can be viewed as semi-formal specifications, and the reuse of specifications encourages software standardization, which means that separately developed software units can often be reused in combination. (ii) L1 takes the further step of allowing more than one implementation of the same type to exist (and even be used together in a single program). This makes it possible for one implementation of a type to reuse another implementation of the same type, without straining the type system. (iii) An implementation of a type can reuse (parts of) more than one other implementation of the same type. (iv) Reused code need not be an implementation of the same type nor of a supertype. This allows flexible code reuse in cases involving code not related by the type hierarchy (cf. mutually_exclusive and read_write_ synchronized), or where subclassing and subtyping run contrary to each other (cf. d_e_queue and queue) (v) The reuse mechanism is complemented by a "hat" mechanism, which allows the code of a different implementation to be bracketed by new code in a different implementation, thus making the bracketed code extensible and reusable. This is
considerably more flexible than the standard super technique, as it is orthogonal to the type system. (vi) The second major difference between L1 and other object-oriented programming languages is its distinction between object types and attribute types. Here the potential for reuse of code is very high. We saw how the attribute loanable could easily be combined with quite different object types, such as book and car. It is easy to envisage how such general purpose attributes can be reused in combination with very many kinds of objects both within a single system (e.g. the library might also have loanable CDs, theses, software packages) and in quite different systems (car hire firms, catering firms, etc.). (vii) Yet a further level of reusability of software is made possible by the idea of bracket routines for attribute types. A particularly good example of this is in the area of synchronization. We have illustrated two simple synchronization approaches (mutual exclusion, reader-writer synchronization), which were not only able to reuse code, but which have a wide range of application in many software systems. This list could easily be extended to include more complex synchronization mechanisms such as Hoare's monitor, path expressions and the like. Normally such code cannot be packed into separate modules and reused at will with any object type, so here the potential for code reuse is very high. All that is needed to synchronize any object is a type definition such as: objtype shareable_library_book isa read_write_synchronized library_book end shareable_library_book
This opens up the possibility of reusing generalized "adjectival" code in a way which is not otherwise possible in object-oriented programming languages.
6
Comparison with Other Work
The distinction between definitions of types and implementations can be traced back to ideas such as information-hiding [22, 23] and specification techniques for abstract data types [15]. More recent work on specification and verification (as found in e.g. OBJ [11], Resolve [26], GenVoca [1] and Lileanna [12]) emphasizes the advantages of this separation in conjunction with static parameterization as a technique which to some degree can be considered as equivalent to inheritance in object oriented languages. In this way, for example, a supertype used parametrically can achieve a similar effect to the "hat" mechanism mentioned in section 2. In fact appropriate uses of templates, which have their roots in Ada, can achieve equivalent results to our specialized brackets, for example as a means of checking pre- and post-conditions [8]. In contrast, our generalized brackets appear to be more flexible, because with these, the names of the bracketed operations need not be known. On the other hand, generalized brackets have a more limited application, being of relevance mainly for ‘system’ purposes, such as synchronization, monitoring and protection. In the object-based and object-oriented traditions the distinction between definitions of types and implementations appears in the languages Emerald [3, 4] and Theta [20]). However, in contrast with L1 and Tau, Theta does not support constructors in type definitions and it appears not to have a mechanism for selecting
implementations. Although the language Sather [29] does not have separate types and implementations as such, it does have separate subtype and code-reuse hierarchies. However, it does not support implementations which are not also types, and, in contrast with L1 and Tau, reused code must be recompiled. As mentioned earlier, a more limited form of specialized bracketing can be achieved in Smalltalk-80 [13] (and in almost all other object-oriented languages) by redefining the methods in a subclass and calling the original methods from within the redefined methods via the super construct. As discussed in section 3.2 languages such as Eiffel [21] with multiple inheritance can, in a rather less attractive way, model attribute types, but there is nothing comparable to generalized bracket routines in such languages. The language Beta [18] is close to the L1 approach for bracket routines. The methods of a class definition may include the special statement inner (similar to body). This results in the same method in a subclass being bracketed by the code of the superclass method. But this mechanism is not very useful for general attributes such as modification_expiring or mutually_exclusive. A class mutually_exclusive would need to know exactly which methods occur in its subclass shareable_library_book in order to bracket them and would therefore be of no use in bracketing shareable_library_cd. Even worse, since Beta supports only single inheritance, shareable_library_book could either inherit from mutually_exclusive or from library_book but not both. Mixins can be seen as a generalization of both the super and the inner constructs. The language CLOS [7] allows mixins as a programming technique without supporting them as a special language construct, but a modification of Modula-3 to support mixins explicitly has also been proposed [5]. A mixin is a class-like modifier which can operate on a class to produce a subclass in a way similar to L1 attribute types. So, for example, a mixin mutually_exclusive can be combined with a class library_book to create a new class shareable_library_book. Bracketing can be achieved by using the 'call-next-method' statement (or super in the Modula-3 proposal) in the code of the mixin methods. As with Beta, however, the names of the methods to be bracketed must be known in the mixin. This again prevents it from being used as the kind of general attribute we intend. Furthermore, it is proposed in [5] that mixins entirely replace classes and that 'classes are viewed as degenerate mixins'. This, together with the fact that mixins do not support a separation of type and implementation, impairs their use for modeling and software engineering. As was discussed in section 3.2 interface types of the language Java [14] are often used as a kind of attribute type. But since independent implementations of adjectival interfaces cannot be combined with the implementations of objects via multiple inheritance, this helps only on the type level and not for code reuse. In [24] encapsulators are described as a novel paradigm for Smalltalk-80 programming. The aim is to define general encapsulating objects (such as a monitor) which can provide pre- and post-actions when a method of the encapsulated object is invoked. This is similar to L1 generalized bracket routines but is based on the assumption that the encapsulator can trap any message it receives at run-time and pass this on to the encapsulated object. This is feasible only for a dynamically typed system. The L1 mechanism can be seen as a way of achieving the same result in a statically type-safe way via a limited form of multiple inheritance. The applications of
encapsulators are also more limited than bracket routines since there is no way for them to distinguish between constructors, enquiries and operations. The similarity between our proposal and the approach of 'aspect-oriented' programming [17] was mentioned in section 3.3. Both the specialized and generalized bracket routines can be viewed as 'aspect weaver' techniques for some aspects of software system design. Finally, the metaphor of adjectives as a design guide has also been emphasized recently in [10].
7
Conclusion
We have attempted to show how the reuse of software can be considerably enhanced by making two unusual distinctions in object oriented programming. Whereas in conventional object oriented programming the idea of a class unites the concepts of type and implementation, these concepts are kept separate and orthogonal in the experimental languages L1 and Tau. The advantages of making this separation for the reuse of code are described in section 2 and are summarized in section 5, points (i) to (v). An unusual technique for achieving a limited form of multiple inheritance, based on a distinction between object types (which can be viewed as corresponding to nouns in natural language) and attribute types (corresponding to adjectives) and its implications for code reuse were described in section 3 and summarized in section 5, points (vi) and (vii). By separating type and implementation definitions a technique for providing specialized bracket routines, and by separating object types and attribute types a technique for providing generalized bracket routines emerged. The differences between these are summarized in section 4 and their relevance for code reuse is mentioned in section 5, points (v) and (vii). It is particularly interesting to note that with generalized brackets it is generally the brackets themselves which are reused, whereas in the case of specialized brackets it is the bracketed code which is reused. Finally in section 6 we briefly referred to work related to that described in this paper. It appears that the potential for reusing code when writing object oriented programs based on the proposed techniques is considerably higher than is possible in current object oriented environments.
References 1. 2. 3. 4.
D. Batory, J. Singhal, J. Thomas, S. Dasari, B. Geraci and M. Sirkin "The GenVoca Model of Software-System Generators", IEEE Software, pp. 89-94, 1994. G. Baumgartner and V. F. Russo "Signatures: A Language Extension for Improving Type Abstraction and Subtype Polymorphism in C++", Software - Practice and Experience, 25, 8, pp. 863-889, 1995. A. Black, N. Hutchinson, E. Jul and H. Levy "Object Structure in the Emerald System", in Proceedings of the OOPSLA '86, Portland, Oregon, Vol. 21, ACM SIGPLAN Notices, 1986. A. Black, N. Hutchinson, E. Jul, H. Levy and L. Carter "Distribution and Abstract Types in Emerald", IEEE Transactions on Software Engineering, SE-13, 1, pp. 65-76, 1987.
5. 6. 7. 8.
9.
10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26.
G. Bracha and W. Cook "Mixin-based Inheritance", in Proceedings of the ECOOP/OOPSLA '90, pp. 303-311, 1990. P. J. Courtois, F. Heymans and D. L. Parnas "Concurrent Control with Readers and Writers", Communications of the ACM, 14, 10, pp. 667-668, 1971. L. DeMichiel and R. Gabriel "The Common Lisp Object System: An Overview", in Proceedings of the ECOOP '87, pp. 151-170, 1987. S. H. Edwards, G. Shakir, M. Sitaraman, B. W. Weide and J. E. Hollingsworth "A Framework for Detecting Interface Violations in Component-Based Software", in Proceedings of the 5th International Conference on Software Reuse, pp. 46-55, IEEE, 1998. M. Evered, J. L. Keedy, A. Schmolitzky and G. Menger "How Well Do Inheritance Mechanisms support Inheritance Concepts?", in Proceedings of the Joint Modular Languages Conference (JMLC) '97, Linz, Austria, Springer-Verlag, Lecture Notes in Computer Science 1204, 1997. M. C. Feathers "Factoring Class Capabilities with Adjectives", Journal of Object Oriented Programming, 12, 1, pp. 28-34, 1999. J. A. Goguen "Parameterized Programming", IEEE Transactions on Software Engineering, SE-10, 5, pp. 528-543, 1984. J. A. Goguen and W. Tracz "An Implementation-Oriented Semantics for Module Composition", in Foundations of Component-based Systems, ed. G. Leavens and M. Sitaraman, Cambridge, 2000. A. Goldberg and D. Robson, Smalltalk-80: The Language and its Implementation, Reading, Mass.: Addison-Wesley, 1983. J. Gosling, B. Joy and G. Steele, The Java Language Specification, Reading, MA: Addison-Wesley, 1996. J. Guttag and J. J. Horning "The Algebraic Specification of Abstract Data Types", Acta Informatica, 10, 1, pp. 27ff., 1978. J. L. Keedy, M. Evered, A. Schmolitzky and G. Menger "Attribute Types and Bracket Implementations", in Proceedings of the 25th International Conference on Technology of Object Oriented Systems, TOOLS 25, Melbourne, pp. 325-337, 1997. G. Kiczales, J. Lamping, A. Mendhekar, C. Maeda, C. Lopes, J.-M. Loingtier and J. Irwin "Aspect-Oriented Programming", in Proceedings of the ECOOP '97, pp. 220-242, 1997. B. B. Kristensen, O. L. Madsen, B. Moller-Pedersen and K. Nygaard "The Beta Programming Language", in Research Directions in Object-Oriented Programming, MIT Press, pp. pp. 7-48, 1987. G. T. Leavens "Modular Specification and Verification of Object-Oriented Programs", IEEE Software, July, pp. 72-80, 1991. B. Liskov, D. Curtis, M. Day, S. Ghemawat, R. Gruber, P. Johnson and A. C. Myers "Theta Reference Manual", Report Number 88, MIT Laboratory for Computer Science, Cambridge, MA, 1994. B. Meyer, Eiffel: the Language, New York: Prentice-Hall, 1992. D. L. Parnas "On the Criteria to be Used in Decomposing Systems into Modules", Comm. ACM, 15, 12, pp. 1053-1058, 1972. D. L. Parnas "A Technique for Module Specification with Examples", Comm. ACM, 15, 5, pp. 330-336, 1972. G. A. Pascoe "Encapsulators: A New Software Paradigm in Smalltalk-80", in Proceedings of the OOPSLA '86, pp. 341-346, 1986. A. Schmolitzky "Ein Modell zur Trennung von Vererbung und Typabstraktion in objektorientierten Sprachen [A Model for Separating Inheritance and Type Abstraction in Object Oriented Languages]", Dr. rer. nat. [Ph.D.] Thesis, University of Ulm, 1999. M. Sitaraman and B. Weide "Component-Based Software Using Resolve", ACM SIGSOFT Software Engineering Notes, 19, 4, pp. 21-67, 1994.
27. A. Snyder "Encapsulation and Inheritance in Object-Oriented Programming Languages", in Proceedings of the OOPSLA '86, Portland, Oregon, Vol. 21, ACM SIGPLAN Notices, 1986. 28. N. Soundarajan and S. Fridella "Inheriting and Modifying Behavior", in Proceedings of the 23rd International Conference on Technology of Object Oriented Systems, TOOLS 23, pp. 148-162, IEEE Computer Society Press, 1998. 29. C. Szyperski, S. Omohundro and S. Murer "Engineering a Programming Language: The Type and Class System of Sather", in Programming Languages and System Architectures, ed. Jurg Gutknecht, Springer Verlag, pp. 208-227, 1993.