Business Object Components?
Department of Computer Science
Lund Institute of Technology
Lund University
Box 118
S-221 00 Lund
Sweden
Abstract: Although the term business objects is notoriously polysemous, most authors making use of it seem to assume that business objects, apart from being objects in the object-oriented sense, should also be reusable, “plug-and-play” components. However, the starting-point of the vision of object-orientation is fundamentally different from that of software components, and objects and components cannot, as is often taken for granted, be fitted together without considerable difficulties. To be more precise, these two programming paradigms clash in the concept of implementation inheritance, which, while being fundamental to most strains of object-oriented programming, viciously undermines the plug-and-play composability required by software componentry, owing to a set of issues commonly, albeit somewhat ineptly, referred to as “the fragile base class problem”. Below, having outlined what class fragility is, I will try to show how it affects business objects and how three important candidate platforms for the implementation of business objects, viz. Newi, Java, and COM+, fare with reference to it. Hereupon will follow a brief description of encapsulated programming, which is a novel style of programming attempting to harmonise object-orientation and software componentry on the basis of encapsulated inheritance. This reformed inheritance mechanism, designed so as to eschew all kinds of class fragility, forms the basis of a new basic building block of software, the capsule, which in effect will assimilate the object and component concepts. Finally, I will also discuss how encapsulated programming can be brought to bear on the three aforementioned candidates for a business objects platform.
Keywords: business objects, software components, encapsulated programming
1 Introduction
Business objects are often held to be, as it were, the ultimate software components, potentially providing for very high levels of reuse and software agility. For such business objects to become viable, various obstacles, will, however, have to be overcome, of which a very serious one, viz. class fragility, provides the topic of this study.
The idea that the assembly of prefabricated parts or components, forming the basis of the industrial production of computer hardware and most other artefacts, can be taken advantage of also in the production of software was broached in Douglas McIlroy’s famous invited address Mass Produced Software Components, delivered at the NATO conference on software engineering in Garmisch in 1968.[1] The basis of such software componentry is the machine metaphor, a conception of a computer programme as a machine-like contrivance built from parts, making encapsulation and reuse the keystones of software construction. In contrast, object-orientation, originating slightly earlier with the SIMULA 67 language, rests on a world metaphor, which arises naturally in the domain of software simulations targeted by the SIMULA language.[2] The allure of object-oriented software – including business objects – largely consists in its embodiment of a mental model of a set of real-world or imaginative entities that renders an object-oriented programme akin to a “microcosm” of message-exchanging objects. In an oddly Platonic vein, the dynamically changing, ‘material world’ of objects inside an executing object-oriented programme mirrors the unchanging, hierarchically structured, ‘ideational world’ of classes sculpted by a programmer demiurge. In sum, the lodestar of object-orientation is hierarchical modelling, that of software componentry the encapsulation of parts. Can these two fundamentally different outlooks be reconciled?
In the 80s, as the interest in object-orientation, software componentry, and software reuse started to grow rapidly, Brad Cox made the ingenious and intriguing attempt to amalgamate the component and object concepts through his notion of Software-ICs.[3] These were binary packaged Objective-C objects, possessing many of the dynamic properties now widely held to be necessary for components. The support for implementation inheritance implied by the object-oriented paradigm, however, gave rise to a number of “fragility” problems that tend to seriously undermine the component idea. Through clever design, Cox attempted to counter the problems, although he was not able to resolve all the issues involved.
Since the early 90s, the suitability of objects as components has become increasingly contentious. In particular, weighty criticism has been levelled at implementation inheritance, the bedrock of object-orientation, but also the source of the fragility problems. For example, Cuno Pfister, the chairman of Oberon microsystems, strikingly declares, “It may well turn out that implementation inheritance is the GOTO statement of the nineties”.[4] Additionally, various problems pertaining to object-oriented frameworks seem to be derivable from implementation inheritance.[5] ‘The fragile base class problem’ caused the designers of Microsoft’s Component Object Model (COM), the as yet most successful component infrastructure, to abandon implementation inheritance altogether. Rival component technologies, such as OpenDoc and JavaBeans, have retained full support for implementation inheritance and the object-oriented approach, thereby, however, also retaining the liability to the class fragility problems. Pace the many advocates of such ‘traditional’ object-orientation, the swiftly rising star of component-oriented programming, as advocated in e.g. Szyperski’s suggestively named book Component Software. Beyond Object-Oriented Programming[6], seems to be beaming in the direction taken by the designers of COM.
Below, I will briefly adumbrate what class fragility is and why it is harmful to the business objects vision. I will then suggest how object-orientation and software componentry can be reconciled through encapsulated programming, a novel programming style that attempts to correct the deficiencies of the object-oriented inheritance mechanism.[7] I will also discuss how the adoption of this programming style would affect classical Newi-style business objects as well as business objects implemented as COM components or as Java language objects, such as JavaBeans.
2 Class Fragility and Business Objects
Inheritance is widely held to be the differentia specifica of object-orientation[8], but has become increasingly debated, as it clearly is also the
source of miscellaneous problems.[9] According to Wegner and others, inheritance is essentially a
mechanism for the “incremental modification” of a superclass by a subclass[10], making possible the programming style called programming-by-difference.[11] Besides inheritance, polymorphism
is generally considered essential to object-oriented programming.[12] Where polymorphism is supported,
values and variables may be of more than a single type, whereas the antonym monomorphism implies that any value
or variable in a programme may be of one and only one type. In object-oriented
programming, polymorphism is yoked closely together with inheritance,
insofar as object references usually are polymorphic
and refer to objects that may be either of the type of the object reference
or of any subtype of this particular
type. In some programming languages that support dynamic typing, such as Smalltalk
or Objective-C, object references
may hold references to any type of object. When polymorphism is taken advantage
of, method resolution will happen at run-time by means of dynamic or late binding.
There are two distinct variants of inheritance, interface inheritance (or subtyping) and implementation inheritance (or subclassing). The former term denotes the inheritance of type (i.e. interface or protocol), the latter the inheritance of behaviour and state (i.e. implementation). Whereas the virtuousness of interface inheritance is not very controversial[13], implementation inheritance has ever and anon been decried as uninteresting[14] or potentially maleficent[15]. Technologies such as COM and CORBA directly support only interface inheritance, whereas most object-oriented programming languages merge interface and implementation inheritance into one concept. An important exception is Java, which offers a clear separation of the two inheritance types. In C++, interface inheritance may be simulated through public inheritance of an abstract class, which has only pure virtual function members, whilst pure implementation inheritance may be simulated with private inheritance.[16]
Inheritance is sometimes said to “break encapsulation”, because it makes superclasses expose implementation details to their subclasses.[17] In this context, it is noteworthy that, in general, a class exposes two interfaces that usually intersect: the client interface intended for ‘ordinary’ run-time usage and the specialisation interface intended for subclassing.[18] In many object-oriented languages, such as C++ and Java, it is possible to restrict access to methods and instance data by the use of access modifiers. In C++ and Java, the methods and data of the client interface are declared public, those of the specialisation interface protected, and implementation details, which are to be accessible through neither interface, private. In both these languages, members of the client interface will always be part of the specialisation interface as well.
Whereas strict observance of the principles of information hiding and data abstraction will mitigate the problem of encapsulation break[19], it will still at times be necessary to have access to the source code of a superclass in order to be able to implement a subclass correctly. In particular, this may be the case when a superclass method is overridden by a method in a subclass or when a subclass defines a method that will be recursively called from the superclass.[20] The cause of this need is that a subclass – in contrast to an ordinary client – will share the important variable self (also known as this) with its superclass. Inheritance-based reuse is at times referred to as white-box reuse, because source code access is required for the specialisation of the reused code. This need for source code access is evidently a serious obstacle on the road to markets for plug-and-play software components or business objects. For software componentry, extensibility mechanisms supporting black-box reuse, such as aggregation, are, hence, frequently claimed to be superior to white-box mechanisms.
2.1 “The Fragile Base Class Problem”
The locution “the fragile base class problem” emerged within Microsoft during the design and development of COM. Strictly speaking, this term seems inappropriate, since, what is “broken” and thus “fragile”, will in most cases be a subclass rather than a base class, although the underlying causes of the fragility often lie with a base class. Some authors use the term also for scenarios, where neither a base class, nor a subclass, but a client of a class is what “breaks”. In my contention, the term “the fragile base class problem” is misleading and should be abandoned. Instead, I suggest class fragility as an overriding term for all fragility problems that pertain to the use of classes; these can be further subdivided into subclass fragility and client fragility.[21]
Moreover, the term “the fragile base class
problem” is made to refer to different phenomena by different authors. Mostly,
the concept is understood as referring to problems that occur when base
class implementations evolve independently of their subclasses, and
this will also be the sense used here.[22] There are two distinct varieties of the problem, viz. syntactic and semantic fragility.[23] Since it will not be possible to explore these problems, which are
both quite intricate and quite technical in nature, in detail here, we will
focus on their consequences for components
and business objects.
2.2 Syntactic Fragility
Source code references to external functions and data (i.e. functions and data defined outside the current source code file) are traditionally resolved at “link time” through static linking by a linker. However, in GUI environments, such as Windows and OS/2, run-time or dynamic linking to Dynamic Link Libraries (DLLs) has caught the wind.[24] At the cost of an extra memory indirection for each external reference, dynamic linking makes it possible to upgrade a DLL without having to re-compile and re-link all applications that make use of it, provided that the functions and the data referred to by these are not removed, renamed, or their types/signatures changed in the new DLL. This is a major benefit, making it easy to distribute fixes and upgrades of binaries shared by a large number of programmes without breaking them. For one thing, the Windows API is distributed as a set of DLLs. Unfortunately, such flexibility is only possible when procedure-oriented languages, such as C, are used; when object-oriented languages are relied upon, the syntactic fragile class problem will stand in the way.
Syntactic fragility will make it necessary to recompile unmodified subclasses and clients, whenever changes in the interface of a base class have been made, lest the subclasses should “break” during execution. Recompilation will be necessary even upon apparently very harmless changes to the interface of the base class, such as a simple reordering of its members. The problems derive from the way virtual methods and instance data are organised in tables and referenced by tabular offsets in the compiled and linked binaries of object-oriented programmes. Unfortunately, the layout and size of these tables, which need to be known at compile-time, will tend to be affected by any modification to the class interface, however slight.
Syntactic fragility causes two problems, recompilation avalanches and the breakdown of release-to-release binary compatibility. Recompilation avalanches tend to affect programmer productivity very adversely, as applications grow large and compilation- and link-times become increasingly protracted. In compiled object-oriented languages, this is exacerbated by the across-the-board use of file inclusion directives and file time stamps to determine which code needs recompilation. In C++ and some other languages, the problem is primarily restricted to class interface changes by a clear-cut separation of the class interface from its implementation through header files, although some implementation details exposed by the interface of a class, such as in-line function members, may occasionally add to the fragility.[25] Languages that do not keep interface and implementation apart in different files will be more vulnerable to syntactic fragility, as not only interface changes, but any changes to the implementation part of a class will trigger the recompilation of subclasses and clients.
The breakdown of release-to-release binary compatibility affects system reliability adversely, inasmuch as the introduction of a new version of an object-oriented DLL will possibly invalidate all applications that rely on this DLL, as the offsets used to address data and virtual function members as well as the table sizes and layouts presumed by any existing subclasses may no longer be correct. This obviously is a major obstacle for plug-and-play software componentry and business objects.
Various attempts to dispel syntactic class fragility have been made. Some of these, such as the run-time method dispatch of Smalltalk-80, Brad Cox’ Software-ICs[26], or IBM’s SOM (System Object Model)[27], only provide partial solutions and in doing so incur a more or less serious performance penalty to boot.
In the Java Virtual Machine (JVM), syntactic fragility is avoided by suspending 1) name resolution to link time (which happens either at load-time or at run-time) and 2) the determination of the storage layout of objects to run-time.[28] Unfortunately, the considerable load-time/run-time performance penalty implied by this approach will have to be paid afresh every time a Java programme is executed and will grow considerably worse with code size and code complexity.[29] The success of Java is largely a consequence of the security benefits of the virtual machine approach for the – not very performance-critical – browser-based execution of code snippets downloaded over the Internet. On the other hand, if the JVM approach is abandoned for compilation to native code, as is often suggested when the performance problems of Java are debated or performance-critical application areas, such as web server or embedded systems programming, are considered, syntactic fragility will necessarily reappear.
Microsoft’s COM (Component Object Model) eschews syntactic fragility through the convention that published COM interfaces must be immutable.[30] Finally, Newi’s cooperative business objects[31], which rely on semantic messaging for intercommunication, are not liable to syntactic fragility at all, since dispatch is made dynamically at message-time in a message loop. Although this kind of semantic messaging is much slower than ordinary method calls, the granularity of Newi’s business objects will ensure that inter-object messaging will be comparatively rare, primarily happening due to direct user interaction.[32] Notably, both COM and Newi introduce a “layered” programming model, where inter-component calls differ significantly from intra-component calls. This two-layered approach allows both these technologies to provide language independence through the definition of a binary interface at the upper component/business object echelon.
2.3 Semantic Fragility
Semantic
fragility is much more elusive in nature than its syntactic sibling, as it arises from evolutionary
changes not to the interface of a
class, but to its implementation. It
may show up, whenever a new version of a base class, from which subclasses
have been derived through implementation
inheritance, is introduced, provided the base class makes a – direct
or indirect – polymorphic self-recursive
down-call. Since every polymorphic
self-call may potentially be resolved into a down-call, we can just as
well say that the problem is latently overhanging in any class that makes at
least one polymorphic self-call. In
addition, it may show up upon revision of a base class, the instance data of
which are directly accessed from a subclass. Mikhajlov and Sekerinski, who
have investigated this problem at length, discern five distinct cases of
it, some of which are quite involved.[33]
These cases will not need to be retold here; the bottom line is that whenever a base class is revised, any (unmodified) code taking advantage of the base class through subclassing may unexpectedly start to malfunction, although care has been taken to ensure that the modifications in the base class will be entirely behaviour-preserving.[34] This will cause a breakdown of release-to-release semantic compatibility and will compel subclass programmers to get access to and analyse the source code of base classes. Just like the lack of release-to-release binary compatibility, this will of course seriously undermine the software componentry and business objects visions.
Since implementation inheritance is a crucial feature of object-oriented languages and these languages eo ipso are liable to semantic fragility, the problem has attracted considerable attention by researchers. Two kinds of countermeasures against semantic fragility that have been suggested are:
· to “discipline” implementation inheritance by the establishment of a set of “rules”
· to abandon implementation inheritance altogether
The former
avenue, which has been dealt with in a number of research papers, seems to lead
up to awkward and byzantine schemes that will force programmers to perform
complex analyses of the source code interdependencies
of methods, method groupings, self calls, etc.[35] Furthermore, these schemes presume that the subclass programmer
has access to the source code of superclasses, which will be highly undesirable
in component/business objects scenarios.
The latter avenue of forgoing implementation
inheritance altogether – taken, for instance, in Microsoft’s COM – will raise the hackles of many advocates of object-orientation accustomed to the
reliance on inheritance as a handy extensibility mechanism.
3 Encapsulated Programming and Business Objects
Elsewhere, we have suggested that the clash
of object-orientation and component-orientation can be resolved
through encapsulated programming,
a new programming style designed so as to be impervious to all kinds of
class fragility, while still retaining support for a restricted, “disciplined”
form of implementation inheritance.[36] Encapsulated
programming is based on two basic mandates:
· Firstly, syntactic fragility is to be eschewed in one way or the other, be it through a semantic messaging mechanism as in Newi, an interface immutability convention as in COM, or the postponement to load-time/run-time of name lookup and of the computation of object layout as in JVM.[37]
· Secondly, semantic fragility is to be addressed through the espousal of encapsulated inheritance, which modifies implementation inheritance by banning polymorphic self-recursive down-calls and direct access to superclass data.[38]
In our terminology, an object that avoids syntactic fragility and supports encapsulated inheritance is called a capsule. Since, in our view at least, class fragility must be overcome, if the visions of software components and business objects are not to remain on the drawing-board, capsules should provide a suitable shape for the implementation of components and business objects.
We will now try to clarify how encapsulated programming will affect business objects, using Newi-style business objects, Java objects, and COM components as examples. We have chosen to look at Java and COM because of their current importance on the market overt, whereas Newi will be considered both because of its historical impact on the business object community through the writings of Oliver Sims, “the father of business objects”, and because of its principal importance.
3.1 Newi-style business objects
Newi’s semantic messaging mechanism effectively immunised Newi classes against syntactic fragility, although, by virtue of its support for implementation inheritance and polymorphic self-recursive down-calls, its objects were amenable to semantic fragility. However, this was somewhat mitigated by the fact that subclasses and clients did not have direct access to superclass data. Thus, by removing the possibility of sending messages to self (but, of course, not to super!) from the Newi infrastructure, Newi could easily have been made to support encapsulated inheritance and the encapsulated programming paradigm.
3.2 Java and JavaBeans
Java code executed in the Java Virtual Machine is not liable to syntactic fragility, although the virtual machine approach may imply a significant performance penalty. This can be countered through dynamic linking and compiling with caching, which is a scheme that refines the JVM approach by caching the linked and verified bytecodes – as well as any snippets of binary code resulting from dynamic compilation – in a cached image that, after a version control, can be taken advantage of on subsequent execution of the code.[39] This approach will, I believe, reduce the performance penalty of the virtual machine approach considerably and still steer clear of syntactic fragility.
In order to avoid semantic fragility, the Java inheritance
mechanism needs an overhaul so as to provide support for encapsulated inheritance. Two minor measures will suffice:
Firstly, all data members should be made private;
thus, one should not be allowed to use the access modifiers protected and public on data members. Secondly, polymorphic self-calls of methods that are neither final, nor private should be disallowed. As the Java designers will not be likely to accept such bold reformation
of their language, one may instead choose to use these rules as guidelines for
the design of Java or JavaBeans classes intended as reusable components or business objects.
In this context, it should be noted that encapsulated programming clashes with
the workings of white-box frameworks,
which tend to make heavy use of polymorphic
down calls in accordance with the so-called Hollywood principle (don’t call us, we call you). Although
certainly controversial, giving up the current framework reliance of the object-oriented
approach appears to us a both necessary and highly beneficial step. In our
experience, the chief cause for the low productivity and steep learning curves
of object-oriented programmers as compared to developers that work with component-based
RAD tools stems from the reliance on application frameworks, which have an
extremely high surface area[40] and thus are very difficult to master and use. Application
frameworks also violate fundamental principles of software engineering,
such as encapsulation, information
hiding, and the minimisation of cross-module coupling, and, thus, tend to breed code complexity, undermine maintainability,
and create dangerously tight couplings between application code and frameworks.[41]
The Java approach is, however, highly framework-oriented, and the thickets of frameworks surrounding the Java language tend to grow increasingly dense every day. For the Java programmer, it will not be a realistic option to dispense with all these frameworks. He can, however, use a two-layer strategy to mitigate the problems by abiding by the principles of encapsulated programming in Java and JavaBeans classes intended as reusable components or business objects, while still taking advantage of the Java frameworks internally in these.
3.3 COM
COM is already immune both to syntactic and to semantic fragility, but lacks support for implementation inheritance. It seems that COM would gain much by getting support for a simple-to-use extension mechanism, such as encapsulated inheritance, since the extension mechanisms presently available to the COM programmer, delegation and aggregation, tend to be both unwieldy and difficult to use correctly.[42]
4 Conclusions
If business objects are to be objects in the object-oriented sense and also capable of being used as plug-and-play components, the problems of class fragility caused by the flawed inheritance mechanism of today’s object-oriented approach must be addressed. We have argued that the fragility problems can be expunged through the adoption of encapsulated programming and outlined how technologies such as Newi, Java, and COM can be modified so as to adhere to this style.
Business objects, being
software representations of the things and entities of ‘the real world’, will
need to be much more large-grained and flexible than the ordinary objects of
object-oriented programming. Although the Newi
product now seems to have faded out of sight in the hands of SSA, I believe that Newi-style, large-granular, directly user-manipulable components
interoperating through semantic messaging as envisioned by Oliver Sims in his
first book[43] still hold great promise as the proper shape for business objects, in particular if
modified according to the lines suggested above. For one thing, various on-going developments, such as the rise
of XML and SOAP and the possibly imminent
arrival of 3-D user interfaces, may
make for a better appreciation of this vision of business objects. By reliance not on proprietary
niche-technology, but on the rapidly maturing COM+, Java, or CORBA technology
compages as infrastructural underpinnings, such business objects should be able to gain in attractiveness in the
marketplace. In the Panopeus project, of which this essay is
a spin-off, I am currently in the process of developing and exploring
the agenda of realistic computing on
the basis of this kind of large-grained business
objects, semantic messaging, 3‑D user interface technology, and encapsulated programming.[44]
5 Acknowledgements
This research has been supported by Crafoordska stiftelsen and Helge Ax:son Johnsons stiftelse.
6 References
[Berr95]