Inheritance helps a programmer create a set of classes for distinct yet similar types of objects. When inheritance is used for subtyping [13], the superclass lists the messages that must be handled by its subclasses. Code that uses superclass messages will work for subclass objects, as long as these objects respond properly to all messages listed in the superclass. Such code will also work without modification (or even recompilation) with objects of subclasses that are added to the system later.
This ability to reuse code as new kinds of objects are added to the system is touted as one of the major advantages of the object-oriented approach. However, it comes at the expense of our ability to reuse code as new operations are added to a system. Consider the general problem of designing software in which various interpretations (i.e. methods or functions) are defined for various kinds of objects (i.e. classes), as discussed by Harrison and Ossher [10], Krishnamurthi, Felleisen, and Friedman [11], and Appel [2, Section 4.2]. For example, Appel focuses on the design of an abstract syntax tree (A.S.T.) for a compiler: The different kinds of objects correspond to various program structures including expressions, statements, and declarations, while the different interpretations include static checks (such as type checking), various optimizations, or code generation for various architectures.
Such a system can be designed in the traditional object-oriented style, which lets us add new kinds of objects. However, new interpretations must be added to the superclass, requiring modification of existing source code and recompilation. The need to add operations to the class also violates a basic principle of data encapsulation, that each class should be defined with a minimal set of operations and edited only for a redesign, not a reuse.
We could, of course, abandon the object-oriented style, and adopt a style in which each function contains code for every type of object it could operate on. This lets us add new interpretations, but the introduction of a new kind of object forces us to edit each of the existing functions. We have once again prevented encapsulation and reuse without recompilation, and lost other benefits of object-oriented style as well. Appel argues that the latter style is more appropriate for his A.S.T. example, while the former is more appropriate for classic object-oriented systems (such as graphical user interfaces).
We demonstrate that this choice of styles represents a limitation of traditional object-oriented languages, not a fundamental design choice. Specifically, it is a consequence of the restriction of dynamic dispatch to the methods listed in a class. If we relax this restriction, we can create a system in which existing code can be reused (without access to source code) as both new kinds of objects and new interpretations are added. We show that it is possible to allow dynamic dispatch for functions not listed in a class (which we call accessory functions of the class), and demonstrate that accessory functions can be implemented efficiently.
This paper is organized as follows: We begin, in Section 2, with a brief review of the use of dynamic dispatch and its impact (both positive and negative) on code reuse. This section also covers the implementation of dynamic dispatch in C++. In Section 3, we spell out the goals that we intend to achieve by generalizing dynamic dispatch, describe the semantics (and C++ syntax) for accessory functions, and show that accessory functions can be used to enhance reuse in our example program. We then discuss, in Section 4, the relationship of accessory functions to other language properties such as support for data encapsulation. In Section 5, we briefly discuss the implementation of accessory functions in C++. Finally, we discuss related work in Section 6, and give our conclusions in Section 7.