Component Integration with Pluggable Composite Adapters

3 downloads 0 Views 458KB Size Report
We propose a new language construct, called a pluggable composite adapter, for expressing component gluing. A pluggable composite adapter allows the ...
Component Integration with Pluggable Composite Adapters Mira Mezini ([email protected]) University of Siegen

Linda Seiter ([email protected]) Santa Clara University

Karl Lieberherr ([email protected])y Northeastern University

Abstract. In this paper we address object-oriented component integration issues. We argue

that traditional framework customization techniques are inappropriate for component-based programming since they lack support for non-invasive, encapsulated, dynamic customization. We propose a new language construct, called a pluggable composite adapter, for expressing component gluing. A pluggable composite adapter allows the separation of customization code from component implementation, resulting in better modularity, exible extensibility, and improved maintenance and understandability. We also discuss alternative realizations of the construct. Keywords: business process integration, separation of concerns, object-oriented frameworks, component-based programming, component integration

1. Introduction Component software, i.e., software that is an assembly of individual, independently developed parts, is becoming the predominant architecture. We consider two factors as the driving force behind this development. First, as indicated in [23] component software represents a middle path between the two extremes that predominate traditional software development: (a) custommade software, i.e., software that is developed from scratch with only the help of tools/libraries and (b) standard software, i.e., prefabricated complete solutions that can only be parameterized to get close enough to what is needed in a particular scenario [23]. The rst approach has the advantage that the resulting software optimally supports the speci c needs of the particular customer, i.e. competitive strategic decisions, as well as changes in the business process. However, it has severe cost, along with maintenance and evolution problems that make it practically obsolete. The opposite is true for the second approach. While it is certainly the more e ective approach, it results in solutions that totally ignore the needs of particular customers and their strategic decisions. Component software combines the advantages of the traditional approaches while avoiding their  Partially sponsored by the National Science Foundation under grant number CDA972057 y Partially sponsored by the Defense Advanced Projects Agency (DARPA) and the Rome Laboratory under agreement number F30602-96-2-0239

c 2000 Kluwer Academic Publishers. Printed in the Netherlands.

main.tex; 12/01/2000; 16:02; p.1

2 problems: most of the individual components can be standard solutions with all the advantages that this brings, while on the other side customer-speci c strategic decisions can be individually satis ed by integrating custom-made parts and adapting the standard components during the assembly process. The second factor in favor of component software is that most of the e ort in developing an enterprise application actually goes into the development of business logic that is shared across the application's vertical domain. Feedback from companies that plan to deliver applications based on IBM's SanFrancisco framework { a Java-based collection of components that allows developers to assemble server-side business applications from existing parts { indicates that as much as 80% of their development cost is spent writing and supporting the basic, non-competitive functions that are essentially the same for any application solution o ered in a speci c domain [21]. In such conditions, the bene ts of concentrating the fabrication of the shared functions in a few components and reusing them across domains is obvious. This observation has actually driven the SanFrancisco project { one of the few server-based component frameworks today. Now, if we consider component-based development to predominate serverbased solutions, the architecture of server-side software resulting from this development will have more or less the layered structure in Fig. 1. The gure is a slightly modi ed version of that representing the structure of SanFranciscobased server systems found in [21], which shows the architecture for Java server-side software. However, the architecture is language independent, provided that server-side component models for component transaction monitors similar to Enterprise JavaBeans [22] are available for these languages.

Figure 1. Server-Side Component Software

main.tex; 12/01/2000; 16:02; p.2

3 The interest of our work is in the boundary between the upper two horizontal layers. Software parts in the core business processes layer (object-oriented frameworks or EJB components in a Java setting, e.g., for the domain of Warehouse Management) are provided by component vendors. They model partial solutions while leaving open application-speci c details. Software building blocks in the top layer are developed by application developers preferably independent of the components in the lower layer. In an end-product, the elements from these two layers obviously need to be coded into a single solution. In order to ensure that full-advantage is taken from the componentbased architecture, it is important that the integration technique satisfy the following requirements: ; The integration process should work with independently developed components. Pre-fabricated components from the core business processes layer may in general be the product of di erent vendors. Furthermore, components in the core business processes layer should be independent of the application-speci c components found in the upper-most layer. The application developer might have legacy code (e.g., class libraries) that he/she wants to integrate into the end-product. This requirement implies that the integration process should tolerate incompatible interfaces among the components to be integrated. ; The integration technique should enable exible (dynamic) recon guration of the integrated system. This implies easy integration of new components into an integrated setting to re ect changes in the business structure, as well as dynamic recon guration to enable dynamic adaptation of the system's behavior. Individual components often come in di erent implementations and it is necessary to dynamically switch between these di erent avors. Furthermore, it makes sense to plug certain components in and out depending on runtime conditions. Cross domain business logic will most likely be modeled as object-oriented frameworks: business processes in the SanFrancisco project are indeed modeled as application frameworks. Thus, the end-product of component-based application development will typically include several frameworks, as well as class libraries containing either legacy code or application-speci c functionality, e.g., encoding the business structure of the particular application. However, traditional framework-based development is based on reuse by extension [12]. Frameworks are generally developed to be deployed by newly written application speci c code. For instance, the techniques used for building SanFrancisco framework-based applications [21] are based on a mixture of subclassing and aggregation (class- and object-based composition). We argue that traditional framework-based development does not properly support the requirements that we posed above for component integration techniques. Similar observations about problems with framework composition are made in [12]. We argue that given the importance of the integration process, object-oriented languages should provide explicit constructs for capturing abstractions in this process. We propose to extend object-oriented

main.tex; 12/01/2000; 16:02; p.3

4 languages with an explicit scoping construct for component integration, called a Pluggable Composite Adapter. Having a dedicated construct for capturing the component integration apart from the code that models business logic improves the modularity of the end-product. Integration of new components into the system is equivalent to de ning a new adapter: the implementation of the existing parts is not a ected by the integration. Adapters are rst-class objects in this model thus supporting the dynamic recon guration of the system. By being themselves components, adapters can be aggregated into more complex building blocks. Furthermore, re nement techniques a la inheritance apply. We have developed a prototype implementation of the Pluggable Composite Adapter in Java. The remainder of the paper is organized as follows. In Sec. 2, we present a running example to demonstrate the shortcomings of traditional component composition techniques. The pluggable composite adapter model is presented in Sec. 3. Sec. 4 brie y presents the current prototype realization of the model in Java and outlines possible alternative realizations. Related work is discussed in Sec. 5. Sec. 6 summarizes the paper and outlines areas of future work.

2. Traditional Object-Oriented Composition Techniques In this section we demonstrate the problems arising from traditional component integration techniques in terms of a running example. A more general discussion of framework composition issues can be found in [12]. Assume we are given a component that encodes the process of price calculation in the domain of order processing1. The component is part of a framework for order processing systems. It is intended to be customized by di erent applications to customer-speci c pricing schemes. abstract class LineItemRole //abstract structure mapping methods abstract PricerRole pricer() abstract CustomerRole customer() abstract ItemRole item()

double basicPrice = pricer().basicPrice(); double discount = pricer().discount(); double unitPrice = basicPrice − (discount * basicPrice); return unitPrice + item().additionalCharge(unitPrice, quantity());

abstract int quantity() //concrete template method double price()

abstract class CustomerRole abstract class ChargerRole

abstract class ItemRole //abstract structure mapping method abstract ChargerRole charge() //concrete template method double additionalCharge(double unitPrice, int qty) { return charge().cost( qty, unitPrice,this ); }

abstract double cost(int qty, double unitPrice, ItemRole item) abstract class PricerRole abstract double basicPrice() abstract double discount()

Figure 2. Pricing Framework Class Model

Fig. 2 contains the pricing framework component. LineItemRole is responsible for calculating the actual price of a line item purchased by a customer.

main.tex; 12/01/2000; 16:02; p.4

5 PricerRole provides price and discount information for the item and customer encapsulated by LineItemRole. ItemRole is responsible for calculating additional charges, de ned in ChargerRole. Fig. 2 also de nes the collaboration required to compute the price of a line item. The price and additionalCharge methods are template methods that de ne the message and data ow of the collaboration. The primitive operations (basicPrice, discount, etc.) are abstract, to be lled in with an application-speci c implementation. Order

quotes

Quote product int quantity

1..*

HWProduct double regPrice() double regDiscount(int qty, Customer c) double salePrice() taxes

customer

1..* Customer double negPrice(HWProduct p) double negDiscount(HWProduct p, int qty)

Tax double taxCharge (int qty, double unitPrice, HWproduct p)

Figure 3. Product Application Class Model

The class diagram modeling the business model of an example application, a product component, is shown in Fig. 3. Assume we wish to deploy the pricing component with the product application according to three pricing schemes. Each scheme requires the application component to conform to the framework component in a di erent way.

; Regular Pricing: Each product has a base price that may be discounted

based on quantity ordered. Quote plays the LineItemRole, while HWProduct plays the ItemRole. In this pricing scheme HWProduct will also play PricerRole, implementing basicPrice, discount for regular pricing by calling regPrice and regDiscount, respectively. Tax plays the ChargerRole, implementing the cost method. Finally, Customer maintains the customer role.

; Negotiated Pricing: A customer may have negotiated certain item prices and discounts. Quote plays the LineItemRole. Customer plays the PricerRole, implementing basicPrice, discount for negotiated pricing. Customer also plays the CustomerRole. HWProduct plays the ItemRole. Finally, Tax plays the ChargerRole.

; Sale Pricing: Each product has a designated sale price and no discounting

is allowed. Quote, HWProduct, Tax and Customer play the same roles as they do with the regular pricing scheme. However, HWProduct will implement basicPrice and discount for sales pricing rather than regular pricing, i.e., by calling salePrice and returning zero respectively.

The traditional framework deployment technique uses static inheritance to model plays-the-role-of mappings. For example, the regular pricing scheme

main.tex; 12/01/2000; 16:02; p.5

6 would require Quote to be rede ned as a subclass of LineItemRole, HWProduct to be rede ned as a subclass of both ItemRole and PricerRole, etc. Note that each of the three pricing schemes requires multiple inheritance. In languages that do not support multiple inheritance, e.g., Java, some of the mappings would be established indirectly using a technique such as the adapter design pattern [5]. Framework deployment using static inheritance has three drawbacks. First, it is invasive in that it requires modi cation of the application classes to encode the customization and the inheritance relationships. Second, it does not encapsulate the multiple roles of the pricing scheme into a single construct, as the roles would be spread out among the various application classes. Third, it is static in that it restricts the product application to a particular pricing implementation at the point of Quote class instantiation. Accommodating all three pricing schemes and allowing an application to dynamically switch between them by applying design patterns [5] results in the design given in Fig. 4. Classes that were present in the original application model are represented in Fig. 4 by the non- lled rectangles. The adapter pattern [5] is used in two places to adapt the interfaces of HWProduct and Customer to their PricerRole (the adapter classes HWProduct PricerRole and Customer PricerRole, respectively). The strategy pattern is used to model the three pricing variants (regular, negotiated, and sale) of the LineItemRole played by Quote. Each of these classes is parameterized with a di erent PricerRole adapter (RegPricer, SalePricer, and Customer PricerRole, respectively). Finally, there will be facade classes for the price calculation according to each scheme. Each of these classes would implement the method price(Quote q) by setting the appropriate strategy and adapter objects for the quote argument, and then invoke price() on it. The facades are not shown in Fig. 4 due to lack of space. The gure indicates several shortcomings of traditional object-oriented framework composition techniques. First, deployment of the pricing functionality with the product application requires the modi cation of the application classes. That is, it is not possible to apply newly acquired business processes to existing objects. Second, the integration of the pricing component results in a proliferation of classes and spurious relations in which the original design gets lost. The clarity of the design is damaged because the code responsible for one customization is not localized in one place, but rather spread around several new adapter, strategy, or facade classes as well as in the original classes. The resulting tangled code is dicult to understand, maintain and extend with new components. This is especially true in a real-life scenario where many business objects and processes can be expected to be involved. To summarize, one could say that the design in Fig. 4 su ers from poor modularity. Recall that it is desirable to non-invasively integrate new components into an application in such a way that the newly integrated functionality can be used with existing objects. This can be realized by advanced design and implementation techniques that combine several simple design patterns. We have presented such a design technique and an elegant implementation

main.tex; 12/01/2000; 16:02; p.6

7

Figure 4. Traditional Framework Deployment

main.tex; 12/01/2000; 16:02; p.7

8 of it in [16]. The technique, called the composite adapter design pattern, is a generalization of the original adapter pattern [5] that supports the adaptation of collaborative functionality. The original adapter pattern, whose structure is given in Fig. 5, solves the problem of adapting the interface of a single pre-de ned class (Adaptee in Fig. 5) to match the interface of a single class in a framework (Target in Fig. 5). Client

Target Request()

adaptee

Adaptee SpecificRequest()

extends Adapter Request()

adaptee.SpecificRequest();

Figure 5. The Structure of the Original GOF Adapter Design Pattern

However, the pattern does not deal with collaborations (composite object adaptation) and the typing issues that arise when one needs to map a set of target classes to a set of adaptee classes. Note that the sample interface used in the structure of the pattern has no return values or parameters. What if the signature of speci cRequest was Speci cReturnType speci cRequest(Speci cArgType1, ... Speci cArgTypen ), where Speci cReturnType and Speci cArgType1 , ..., Speci cArgTypen are types in the domain of the class model that de nes Adaptee? These issues are not considered by the original adapter pattern, however they are solved by the composite adapter technique described in [16]. The composite adapter technique provides a means for integrating collaborative functionality and business processes into existing applications. Fig. 6 shows the composite adapter design pattern technique applied to the integration of sample Java framework and application components. We summarize the technique as follows. Assume we want to integrate the functionality implemented in the Framework package into the application class model given in the Application package. There will be a composite adapter responsible for this integration (App Frm CompositeAdapter), which will have a nested class adapter de ned for each framework class (RootAdapter, ChildAdapter). Each nested adapter extends a framework class, implementing it in terms of an application class. Thus, each nested adapter maps the interface of an application class (referenced by adaptee) to the interface of the framework class that it extends. The nested adapters RootAdapter and ChildAdapter are essentially adapters in the sense of the original adapter pattern [5]. However, the nested adapters must also take care that application objects (AppRoot, AppChild) that come into the scope of the nested code must be wrapped by the corresponding class adaptation (RootAdapter, ChildAdapter) before being operated on. The class adaptations represent dynamic extensions of the application classes, thus the application class instances should appear to acquire their dynamic types while they are referenced within the composite adapter implementations. The

main.tex; 12/01/2000; 16:02; p.8

9

Figure 6. Framework Deployment with the Composite Adapter

main.tex; 12/01/2000; 16:02; p.9

10 application objects acquire their dynamic types by being wrapped by the nested class adapter objects (see e.g., the implementation of frmChild() in RootAdapter in Fig. 6. The creation of class adapter objects is done by factories that memoize the established adapter-adaptee relations, (RootAdapter Factory and ChildAdapter Factory shown in the dashed rectangles in Fig. 6 to indicate that they are created within App Frm CompositeAdapter as anonymous inner classes of the prede ned AdapterFactory). The solution in Fig 6 does indeed satisfy the requirement that the application can be developed independently from the framework2. However, the structure of the adapter is complex and the technique is too advanced to expect it to become a common tool in the toolbox of an average programmer. One should not forget that patterns are conventions that need to be followed, as such they are not enforced by the language and cannot simply be assumed by average programmers [8, 2]. A better alternative would be to establish some kind of \plays-the-role-of" wiring relationship between classes in di erent components independently of the component implementations. It is exactly here that the Pluggable Composite Adapter construct comes into play.

3. Pluggable Composite Adapters Given the importance of the integration process in component-based development, we argue that it should be supported by dedicated language constructs, and propose to extend object-oriented languages with the pluggable composite adapter construct to serve this need. Before outlining the features of this construct, however, let us rst brie y de ne the term component as it is understood in the context of this paper. As indicated by the discussion so far, we understand a component to be a set of (collaborating) classes that de ne some (incomplete) functionality. In general, a class model could be captured by some module construct such as a Java package. However, it is preferable for a component to be a closed entity with well de ned expected and provided interfaces [23, 15]. Java packages are not closed entities in the sense that one can change the interface of a package after it is de ned by simply implementing new classes and declaring them as members of the package. Examples of closed component constructs are Adaptive Plug & Play Components (AP & PCs) proposed in [15], and the generalized class model in Java, where classes can be nested into other classes. In this section we will indicate the advantages of having a component be a closed entity. However, to avoid restricting the applicability of the composite adapter construct to non-mainstream models such as AP & PCs [15] or making the use of Java inner classes a precondition, in the following we will use the term component as equivalent to package and silently assume that once a package component is de ned its interface remains stable. Having clari ed our use of the term component, let us now turn back to the composite adapter construct. In its simplest form, a pluggable composite adapter de nes how to extend a component C1 with a new concrete collaboration. This use of the pluggable composite adapter is also called component

main.tex; 12/01/2000; 16:02; p.10

11 adaptation. In addition, a pluggable composite adapter may de ne how to glue together two independently developed components C1 and C2 , where C2 is an abstract collaboration. The gluing extends C1 with a concrete realization of the abstract collaboration C2 in terms of the functionality provided by C1 . We call this use of the pluggable composite adapter construct component gluing. A collaboration involves a set of objects that work together to implement a task, where each object plays a particular role. Hence, extending a component with a collaboration will require the adaptation of several classes in the component to the appropriate collaboration roles. New variable, method, and class de nitions might be needed in order to realize the component adaptation. Hence, the structure of a pluggable composite adapter presented in Fig. 7. The nested adapters (i.e., adapter R adapts C1 .B [extends C2 .S] adaptation body in Fig. 7) are called class adapters because they specify the adaptation of a single class B of component C1 to its role R. The enclosing adapter A is called a composite adapter, as it nests a set of class adapters. The component C1 is referred to as the base component (hence the denotation B for classes in this component). In the case of component gluing, the abstract collaboration C2 that the base component is being adapted to is referred to as the super component (hence the denotation S for its classes). The attribute pluggable re ects the fact that the component adaptation / gluing encoded within a composite adapter may be plugged in and out as needed. Several alternative realizations of the composite adapter construct may di er on whether the process of plugging in/out is dynamic or not. In this section we present the dynamic avor of the composite adapter, while alternative avors will be brie y outlined in the following section. \Dynamic" means: C1 objects are adapted on the y to play the roles in the collaboration without physically changing C1 's classes. A composite adapter's instances will dynamically lift instances of classes in the base component to the types de ned in the nested class adapters (hence, to the types in the super component in the case of component gluing, since the latter will be supertypes of the nested class adapters). A composite adapter's instances will likewise dynamically lower the previously lifted base instances, restoring them to their original types as necessary. In other words, the composite adapter serves as a \simulation" of the abstract model it de nes or extends in terms of the concrete model of component C1 . The extends clause is put within brackets in Fig. 7 to indicate that it is optional. In the following, we present the main concepts of the pluggable composite adapter model by considering rst the case when there is no extends clause, i.e., the case of dynamic component adaptation in which the adapter directly implements a concrete collaboration. We will subsequently consider dynamic component gluing in which the extends clause will be used to support the concrete realization of the abstract collaboration given in the super component.

main.tex; 12/01/2000; 16:02; p.11

12

adapter A f Field Method Defs Helper Class Defs f adapter R adapts C1 .B [ extends C2 .S ] adaptation body g 

g

Figure 7. The Structure of the Pluggable Composite Adapter Construct

3.1. Dynamic component adaptation In Fig. 7, a class adapter adapter R adapts C1 .B adaptation body implies an adaptation of the functionality of base component class C1 .B by the code in the adaptation body. The adaptation body encodes the delta by which we would have to enhance the de nition of class C1 .B to play the given collaboration role R if we used static subclassing, or if we modi ed the implementation of C1 .B in-place. However, the dynamic version of the adapts relation implies neither an in-place modi cation of C1 .B, nor does it create a new subclass of C1 .B. Via the adapts relation we de ne a \dynamic" extension of C1 .B, meaning that (existing) objects b: C1 .B appear to acquire the extension given in adaptation body. The adaptation body can be thought of as written in terms of three \self variables" (with the following precedence): R.this (the role environment), B.this (the base or adaptee environment), and A.this (the composite adapter environment). All three self variables provide environments for binding variable and method de nitions. That is, (a) the de nitions in the nested class adapter R, (b) the de nitions in base component class C1 .B, and (c) the de nitions in the enclosing composite adapter A (Field Method Defs, Helper Class Defs) are within the scope of the adaptation body. For illustration, Fig. 8 shows an example of component adaptation in which the regular pricing collaboration is being de ned for the product component (cf. Fig. 3). The composite adapter Reg Pricing Product in Fig. 8 implements a nested class adapter for each role in the collaboration, namely LineItemRole, CustomerRole, ItemRole, ChargerRole and PricerRole. Each nested class adapter implements a collaboration role in terms of a Product component class, speci ed using the adapts relation. In this example, we assume the abstract pricing framework of Fig. 2 has not yet been developed. Thus, we are intentionally hardwiring the regular pricing algorithm within the Reg Pricing Product adapter { the methods price() within LineItemRole and additionalCharge(...) within ItemRole. Note how the adaptation body within nested class adapters, e.g., LineItemRole has three implicit self references: LineItemRole.this, Quote.this, and Reg Pricing Product.this. For instance, the price() method in LineItemRole calls operations de ned in LineItemRole itself, e.g., pricer(), while item() calls the product() operation de ned in Quote. If there were composite adapter level method or variable de nitions they could as well be referred to from within any adaptation body via Reg Pricing Product.this. As long as there are no ambiguities self references remain implicit. For instance, we simply call prod-

main.tex; 12/01/2000; 16:02; p.12

13 import product ; adapter Reg Pricing Product f adapter LineItemRole adapts Quote f protected ItemRole item ( ) f return product (); g protected CustomerRole customer () f return customer (); g protected PricerRole p r i c e r ( ) f PricerRole pr = product ( ) ; pr . setQty ( Quote . this . quantity ( ) ) ; pr . setCustomer ( Quote . this . customer ( ) ) ; return pr ; g protected int quantity ( ) f return Quote . this . quantity (); g public double price () f double basicPrice = p ri ce r ( ) . basicPrice ( ) ; double discount = pricer ( ) . discount ( ) ; double unitPrice = basicPrice ; ( discount  basicPrice ); return unitPrice + item ( ) . additionalCharge ( unitPrice , quantity ( ) ) ; g g adapter CustomerRole adapts Customer f g adapter ItemRole adapts HWProduct f protected ChargerRole charge () f return tax (); g double additionalCharge ( double unitPrice , int qty ) f return charge ( ) . cost ( qty , unitPrice , this ); g g adapter ChargerRole adapts Tax f protected double cost ( int qty , double unitPrice , ItemRole item ) f return taxCharge ( qty , unitPrice , item ); g g adapter PricerRole adapts HWProduct f private int qty ; private Customer customer ; public void setQty ( int quantity ) f qty = quantity ; g public void setCustomer ( Customer cust ) f customer = cust ; g protected double basicPrice () f return regPrice (); g protected double discount () f return regDiscount ( qty , customer ); g g g Figure 8. Regular Pricing - Dynamic Component Adaptation

uct() within the implementation of item() in LineItemRole, implicitly meaning Quote.this.product(). If there are ambiguities, as in the case of the quantity() operation which is de ned in both LineItemRole and Quote, we override the default precedence of the self reference, i.e., LineItemRole.this by explicitly delegating to the Quote.this self reference. In contrast to the other two self variables, Reg Pricing Product.this has a second role in addition to serving as a name binding environment. The second

main.tex; 12/01/2000; 16:02; p.13

14 role of Reg Pricing Product.this is in the implicit type lifting/lowering within the adapter scope. For illustration, consider the implementation of item() in the LineItemRole class adapter. One might expect a type mismatch between the result of invoking product() { assuming it to be HWProduct { and the return type of item(), which is ItemRole. However, there is no such con ict. This is because within Reg Pricing Product the type ItemRole is de ned as an adaptation of the base type HWProduct. As the result, the composite adapter views any HWProduct that comes into its scope, e.g., the return value of calling product(), as automatically acquiring its item role in the pricing collaboration { the base object returned by product() will be automatically lifted to the expected ItemRole. The key point in understanding type lifting/lowering is that individual class adapters make sense only within the scope of the enclosing adapter. The latter is not merely a syntactic construct for encapsulating adaptations for the classes involved in a collaboration (in general, a business process). The enclosing class adapter has an important semantical function: it de nes a "functor" that converts types from the base component's domain to the types de ned within the nested class adapters (and thus to the types in the super component's domain in the case of component gluing) and vice versa. In other words, the composite adapter lifts objects from the base component's domain as they come into its scope, and subsequently lowers them as they leave its scope. In the following, we informally describe the semantics of type lifting / lowering and the role that A.this plays in this process.

C1 K * P (A CA) adaptationsOf 8> fR1 ; :::; RnAg(B()f=B; R1g; :::; fB; Rn g 2 adaptedToA) ^ < (9=R0 2 A CAnfR1 ; :::; Rn g; fB; R0 g 2 adaptedToA) adaptationsOf:

>: ?

otherwise

adaptedToA is a relation in C1 K  A CA, where C1 K is the domain of classes de ned in C1 , and A CA is the domain of class adaptations de ned in A.

A  C1 K  A CA : 8 C1 :B 2 C1 K; A:R 2 A CA; C1 :B adaptedToA A:R () adapter R adapts C1 :SB [extends C2 :S ] adaptation body 2 A; where : C1 :B C1 K C1 :SB ^ 9= C1 :SB 0 2 C1 K; s: t: C1 :B C1 K C1 :SB 0 C1 K C1 :SB with C1 K be the subtype hierarchy in C1

adaptedTo

Figure 9. The Composite Adapter Type Environment

In addition to binding composite adapter level variable and method de nitions, the composite adapter self reference A.this also provides an environment for resolving type references within the composite adapter. This type environment is de ned by the adaptationsOfA function in Fig. 9 which maps types

main.tex; 12/01/2000; 16:02; p.14

15 from the base component C1 's domain to sets of types in the composite adapter A's domain. Given, C1 .B, adaptationsOfA (B) includes any A.R de ned as an adaptation of either C1 .B itself or one of its superclasses in C1 . For instance, given the composite adapter Reg Pricing Product in Fig. 8 (RPP for short), adaptationsOfRPP (HWProduct) is the set f ItemRole, PricerRole g. Given the type environment A.this of a composite adapter A, type conversions within A obey the rules given in the following paragraphs for type lifting and type lowering.

Type lifting Let b be an object of the base type C1 .B and assume that b comes into the scope of an adaptation body nested within adapter A in a context where an object of type A.R (or of the supertype of A.R in C2 , if A.R is de ned by an adapts ... extends construct) is expected. An object of base type C1 .B comes into the scope of an adaptation body as the result of either (a) invoking an operation that returns an object of type C1 .B, or (b) directly instantiating C1 .B. If A.R 2 adaptationsOfA(C1 .B) then b will automatically be lifted to the role type A.R. Otherwise, a compile-time error occurs. For illustration, consider the methods item() and pricer() in LineItemRole in Fig. 8. They both call product() operation from Quote. Both invocations of product() cause a HWProduct object to come into the scope of the composite adapter Reg Pricing Product. However, within item() the HWProduct object will be lifted to ItemRole since it comes into scope in a context where an ItemRole instance is expected, while within pricer() the same HWProduct object will be lifted to PricerRole since it comes into scope in a context where a PricerRole object is expected. Both liftings are possible, hence the code is valid, since both PricerRole and ItemRole are adaptations of HWProduct. Lifting b: C1 .B to A.R means (a) creating an instance of R, i.e, an environment R.this, and (b) establishing the relations between the newly created role object R.this, its corresponding base object B.this, and the enclosing composite adapter instance within which the lifting takes place A.this. Hence, at this point all three \self variables" implicitly referred to within the adaptation body of R are bound. The relationship between R.this and A.this is essentially the conceptual relation between a Java inner class instance and the enclosing toplevel class instance. The relationship between R.this and B.this is an aggregation in the current realization of the adapter construct. Other alternatives are discussed in the following section. Finally, it should be noted that the composite adapter A \remembers" the result of lifting a base object b: C1 .B to its role A.R. That is, if b goes in and out of the scope of A several times during the execution of operations de ned in A, it will not be lifted to di erent A.R role adapter objects, i.e., the invocation history in the context of A does not get lost. This is crucial as the class adapter may add role-based state to the base object.

main.tex; 12/01/2000; 16:02; p.15

16 Type lowering

Any class adapter object r: A.R that was created as the result of lifting base object b:C1 .B to A.R should be lowered back to type C1 .B before it can leave the adapter scope. This occurs when within the adaptation body of some nested class adaptation the role object r is either (a) passed as a parameter into an operation de ned in any of the classes in base component C1 , or (b) an operation de ned in class C1 .B is directly invoked on the role object r. Lowering r back to type C1 .B results in the original base object b. For illustration, consider the implementation of cost(int qty, double unitPrice, ItemRole item) within ChargerRole in Fig. 8. Passing cost's argument item as the third actual parameter to Tax.taxCharge(int qty, double unitPrice, HWProduct p) implies an automatic lowering of item: ItemRole to its base product.HWProduct object.







The commutative diagram in Fig. 10, summarizes the relation between the type lifting and lowering performed within the scope of a composite adapter. In the diagram ASR (Adapter-level Structural Relation) stands for a structural relation between two types de ned in a composite adapter, A.R and A.R', whereby the relation is realised via a structure mapping method. For instance, item: LineItemRole ! ItemRole, item(li) = li.item(), is an example for an ASR (cf. adapter in Fig. 8). On the other hand, BSR (Base-level Structural Relation) is the (eventually computed) structural relation between two base types, C1 .B and C1 .B', where R 2 adaptationsOfA(B) and R' 2 adaptationsOfA(B'), that implements ASR. For instance, product: Quote ! HWProduct, product(q) = q.product() is the base structural relation that implements the adapter structural relation item. Given b: C1 .B, the diagram imposes that BSR(b) = b': C1 .B' = lower(ASR(lift(b))). A.R

ASR

lift

C1.B

A.R’ lower

BSR

C1.B’

Figure 10. The Relation between Type Lifting and Lowering Operations

Plugging adapters in at runtime

Given a composite adapter A that adds a collaboration to a component C1 and assuming that the root of the collaboration is a class adapter RR (for root role), a composite adapter instance a: A is dynamically applied to a base object b: C1 .B, where RR 2 adaptationsOfA(C1 .B), by means of the liftTo operator (b liftTo a). Note that we are assuming that there is a single adaptation class for

main.tex; 12/01/2000; 16:02; p.16

17 C1 .B in A. If there are several class adaptations for C1 .B in A, the application operator would explicitly specify which one to use: b liftTo a.aRoleTypeName. As the result of the adapter application, a new class adapter instance for RR gets created. Assuming that the root role class adapter de nes the public method m, we can invoke m on the base instance by executing the invocation expression (b liftTo a).m(). The adapter a: A transforms the base objects of component C1 that are encountered during the execution of method m into their lifted types in a "lazy way": new class adapter instances are created only when base objects come into a's scope. All class adapter instances created this way share the same composite adapter. For illustration, Fig. 11 contains sample client code that dynamically plugs in the functionality of the Reg Pricing Product adapter. This allows the regular pricing functionality to be available to the quote object q.

public class Client f static public void main( String [ ] args ) f Quote q = new Quote ( . . . ) ; Reg Pricing Product rpp = new Reg Pricing Product ( ) ; System . out . println (( q li ftTo rpp ). price ( ) ) ; g g Figure 11. Class Adaptation

3.2. Dynamic component gluing Given components C1 and C2 , the existence of an extends clause in a nested class adaptation f R adapts C1 .B extends C2 .S ... g puts the classes C1 .B and C2 .S in a target-adaptee relation in terms of the adapter design pattern [5]. The nested class adaptation R expresses the relationship C1 .B plays-the-roleof C2 .S. The adaptation body de ned in class adapter R encodes the delta by which we would have to enhance the de nition of C1 .B if we used static subclassing to express the C1 .B plays-the-role-of C2 .S relationship. For illustration, the composite adapter for gluing the concrete product application of Fig. 3 and the abstract pricing framework component of Fig. 2 according to the regular pricing scheme is given in Fig. 12. While the composite adapter in Fig. 8 directly implemented the regular pricing collaboration, the adapter in Fig. 12 only customizes the abstract part of the pricing framework of Fig. 2 in terms of the Product component and inherits the collaboration encoded by the methods price() and additionalCharge(...). Note again the implicit rebinding of types within the adapter scope. For instance, consider the return type ItemRole of item() and the return type HWProduct of the product() method that is called within item(). There is no con ict here because the type HWProduct of the object returned by product() will automatically be lifted to its extension HWProduct ItemRole de ned within the enclosing adapter, Reg Pricing Product, Since HWProduct ItemRole is a subtype of ItemRole, the return type of item() is substitutable for the return type of product().

main.tex; 12/01/2000; 16:02; p.17

18 import product ; import pricing ; adapter Reg Pricing Product f adapter Quote LineItemRole adapts Quote extends LineItemRole f protected ItemRole item () f return product (); g protected CustomerRole customer () f return customer (); g protected PricerRole p r i c e r () f return product (); g protected int quantity () f return Quote . this . quantity ( ); g adapter HWProduct PricerRole adapts HWProduct extends PricerRole f public double basicPrice () f return regPrice () ; g public double discount () f return regDiscount ( quantity ( ) , customer ( ) ) ; g g g adapter Customer CustomerRole adapts Customer extends CustomerRole f g adapter HWProduct ItemRole adapts HWProduct extends ItemRole f protected ChargerRole charge () f return tax (); g g adapter Tax ChargerRole adapts Tax extends ChargerRole f public double cost ( int qty , double unitPrice , ItemRole item ) f return taxCharge ( qty , unitPrice , item ); g g g Figure 12. Regular Pricing - Dynamic Component Gluing

Note that in Fig. 12 the class adapter PricerRole is nested within class adapter LineItemRole. Nesting class adapters into each other helps manage scoping issues. An inner class adapter has visibility for the de nitions in the adaptation body of the enclosing class adapter, just like inner classes in Java can access de nitions in the outer class. Clearly, nested class adapters can also be used for dynamic component adaptation { we could have made PricerRole in Fig. 8 also a nested class adapter of LineItemRole. However, we prefered not to do so for the sake of keeping the discussion at that point as simple as possible and the reader's attention focused on the key features of the composite adapter construct, rather than being distracted by scoping issues. In Fig. 8 instance variables are used in the de nition of PricerRole to refer to the customer and the quantity values, for which a PricerRole calculates the basic and discount prices. These variables are initialized when a new PricerRole is created by a LineItemRole. The same values are brought more elegantly into the scope PricerRole instances in the implementation in Fig. 12.

main.tex; 12/01/2000; 16:02; p.18

19 By making PricerRole an inner role of LineItemRole, each PricerRole instance automatically shares the item, customer, and quantity values of its enclosing LineItemRole instance. Both implementations are valid, since the design of the pricing framework in Fig. 2 simply indicates that given the item, customer and quantity values of a line item, the PricerRole will calculate a basic price and a discount, while leaving open how these values are brought into the scope of the PricerRole. 3.3. The adapter construct at work In the following, we show how the adapter construct improves the modularity and extensibility of systems that result from assembling several components (business processes) in terms of our running example. Along the way, new features of the pluggable composite adapter model will be introduced as needed. The main advantage of the adapter construct is that it facilitates the separation of concerns (functional ingredients of a system are separated from each other as well as the gluing concerns). This leads to improved modularity, hence, readability, maintainability, and more exible extensibility. For illustrating these claims let us compare the e ort required to integrate a new pricing scheme (in general a new business process or multi-object collaborative functionality) when using traditional composition techniques versus an adapter construct. Assume that we have integrated the product and pricing components (cf. see the class models in Fig. 2 and 3) according to the regular pricing scheme. During this integration we have not anticipated integrations of alternative schemes. Thus, the class model will include only the original product classes modi ed as subclasses of the corresponding framework classes, along with the single adapter class HWProduct PricerRole (cf. see Fig. 4). Now, assume we want to extend the system to support sale pricing. This extension will a ect several places in the design in Fig. 4, requiring a subclass of HWProduct PricerRole to be added, Quote to be modi ed to use a strategy object, a strategy class for PricingScheme to be added, along with the appropriate subclasses. Alternatively, in our model we can simply de ne a new composite adapter Sale Pricing Product by incrementally re ning the existing composite adapter Reg Pricing Product that was given in Fig. 12. The new adapter is shown in Fig. 13. Adapters as extensions of other adapters

The meaning of the extends relationship between adapters is similar to the extends relation between classes in Java. De ning an adapter SA as a subadapter of adapter A means that SA (a) inherits all nested class adapters from A, (b) can de ne new nested class adapters, and (c) can override and/or incrementally re ne class adapters nested within A. For instance, Sale Pricing Product inherits the Customer CustomerRole, Tax ChargerRole and HWProduct ItemRole class adapters de ned in the Reg Pricing Product, while re ning the de nition of Quote LineItemRole and overriding the HWProduct PricerRole class adapter nested within Quote LineItemRole.

main.tex; 12/01/2000; 16:02; p.19

20 import product ; import pricing ; adapter Sale Pricing Product extends Reg Pricing Product f adapter Quote LineItemRole extends Reg Pricing Product . Quote LineItemRole f adapter HWProduct PricerRole adapts HWProduct extends PricerRole f public double basicPrice () f return sal ePri ce ( ); g public double discount () f return 0 ; g g g g Figure 13. Sale Pricing Composite Adapter

One can factor out the commonalities of all pricing schemes in an abstract adapter { Pricing Product in Fig. 14. Reg Pricing Product is then de ned as an extension of it, by (a) incrementally re ning the Quote LineItemRole adapter with an implementation for pricer() and (b) de ning a new class adapter, HWProduct PricerRole nested within Quote LineItemRole. A composite adapter is abstract if (a) one of its nested class adapters is abstract, or (b) there is an abstract class in the super collaboration for which no class adapter is de ned within the composite adapter. For instance, Pricing Product in Fig. 14 is abstract because the class adapter Quote LineItemRole is abstract (the abstract method pricer() from LineItemRole remains unimplemented). At the beginning of this section, we mentioned that it is preferable that a component construct be a closed entity with well-de ned interfaces. One of the bene ts of this feature is that the adapter compiler can check whether an adapter is abstract based on the criteria (a) and (b) above. Adapting an adapter

Adapters can also adapt other adapters, a feature that becomes very handy when business processes are layered on top of each other. An illustrative scenario is the following. Assume that after we have extended the bare business model of our hardware products supplier (cf. Fig. 3) with the pricing functionality (cf. Fig. 2), the integrated system needs to be further extended with the ability to calculate the total price of a given Order. Furthermore, assume that the generic component (a mini-framework) whose class model is given in Fig. 15 is available for calculating the sum of a certain value in a composite object structure. Summing is modeled in the diagram in Fig. 15 as the template method sum() in Composite, while what is summed is left open (the method value() in Elements is left abstract), as it will vary in di erent contexts where the summing component might get used. We would like to reuse the summing component in calculating the total of (regular, sale, or negotiated) price of an order. Finally, an order is a composite of quotes and after having integrated the pricing framework into the

main.tex; 12/01/2000; 16:02; p.20

21 import product ; import pricing ; abstract adapter Pricing Product f abstract adapter Quote LineItemRole adapts Quote extends LineItemRole f protected ItemRole item ( ) f return product ( ) ; g protected CustomerRole customer ( ) f return customer (); g protected int quantity ( ) f return Quote . this . quantity ( ) ; g g adapter Customer CustomerRole adapts Customer extends CustomerRole f /  as in Fig . 12  / g adapter HWProduct ItemRole adapts HWProduct extends ItemRole f /  as in Fig . 12  / g adapter Tax ChargerRole adapts Tax extends ChargerRole f /  as in Fig . 12  / g g adapter Reg Pricing Product extends Pricing Product f adapter Quote LineItemRole extends Pricing Product . Quote LineItemRole f protected PricerRole p r i c e r ( ) f return product ( ) ; g adapter HWProduct PricerRole adapts HWProduct extends PricerRole f /  as in Fig . 12  / g g g Figure 14. Extension of Abstract Pricing Composite Adapter

product package, we know that quotes can be lifted into line items, hence we can calculate their price which will be the value to sum. To integrate the summing component into the system (consisting of the product component integrated with the pricing collaboration) we de ne the new composite adapter Summing Pricing Product given in Fig. 16. Note the declaration of Summing Pricing Product as an adaptation of the previously de ned abstract composite adapter Pricing Product. A composite adapter A that adapts another composite adapter BA dynamically extends BA instances with new nested class adapters, which may adapt either (a) BA's base component's classes, or (b) BA's class adapters themselves. For instance, Summing Pricing Product de nes two class adapters: Order Composite adapts the Order class in the base component of Pricing Product, while Quote Element adapts the class adapter Quote LineItemRole de ned in Pricing Product.

main.tex; 12/01/2000; 16:02; p.21

22

Figure 15. Summing Component

import product ; import summing; adapter Summing Pricing Product adapts Pricing Product f adapter Order Composite adapts Order extends Composite f Elements [ ] elements () f return quotes (); g g adapter Quote Element adapts Quote LineItemRole extends Element f double value () f return price ( ); g g g Figure 16. Integration of Summing with Pricing and Product (1)

Note that a top-level adapts relation has similar dynamic semantics as the class-level adapts relation. That is, instances of concrete subadapters of Pricing Product will appear to dynamically acquire class adaptations de ned in Summing Pricing Product. Any composite adapter instance of type Pricing Product that gets lifted to the type Summing Pricing Product is enabled to (a) view any instance of the base type Order within its scope as being of type Order Composite, i.e., of type Composite with a particular implementation of the abstract method elements() (a feature that is not supported by a \pure" Summing Pricing Product composite adapter), and (b) view any class adapter instance of type Quote LineItemRole within its scope as automatically acquiring the ability to also play the role of a summing Element as de ned by Quote Element. This results, e.g., in a double type lifting performed on the elements of the array returned by the invocation of quotes() within the implementation of elements() in Order Composite. As de ned in the class product.Order, the method quotes() returns an array of Quote objects. However, since quotes() is being executed within the scope of a composite adapter of type Pricing Product each Quote object q contained in the returned array gets automatically lifted to a ql: Quote LineItemRole The latter (ql: Quote LineItemRole) gets further lifted to a qe: Quote Element, since the composite adapter within whose scope

main.tex; 12/01/2000; 16:02; p.22

23 it is created is not a pure Pricing Product, but rather one that is lifted to the type Summing Pricing Product. We could have as well used the extends rather than the adapts relationship between adapters in order to de ne Summing Pricing Product. However, adapts is in this case preferable in order to avoid extending all Pricing Product subadapters. Using the static extends rather than the dynamic adapts relation would result in (a) a proliferation of adapters: Summing Reg Pricing Product, Summing Neg Pricing Product, Summing Sale Pricing Product, and (b) duplication of the code in the adaptation body of Summing Pricing Product in Fig. 16. Using the adapts relation rather than extends allows us to reuse the adaptation encoded within the body of Summing Pricing Product with instances of all subadapters of Pricing Product. This is illustrated by the sample client code in Fig. 17. public class Client f static public void main( String [ ] args ) f Order o = new Order ( ) ; Reg Pricing Product regpp = new Reg Pricing Product ( ) ; Sale Pricing Product salepp = new Sale Pricing Product ( ) ; Summing Pricing Product summpp = new Summing Pricing Product ( ) ; g

g

... System . out . println (( o lif tT o ( regpp l i ftTo sumpp ) ) . tota l ( ) ) ; System . out . println (( o lif tT o ( salepp l i ftTo sumpp ) ) . to tal ( ) ) ;

Figure 17. Using the Summing Pricing Product Adapter

Recall that (b liftTo a) causes the base object b to be lifted to the role de ned for its class in the composite adapter A. Thus, in Fig. 17 we rst "adapt" the Reg Pricing Product adapter regpp by the Summing Pricing Product adapter sumpp. The resulting adapter is then used to lift the Order object, with the total() method invoked on the adapted order object. A similar process applies for sales pricing. In the discussion so far the base component has been a concrete application. Adapters can also be used to glue together two abstract collaborations, as illustrated in Fig 18. Now, we rst glue together the abstract collaborations de ned in the summing and pricing packages, resulting in the abstract composite collaboration Total Pricing and then glue the latter with the product package by specifying Total Pricing Product and its subadapters.

4. Alternative Realizations of the Adapter Construct Alternative realizations of the adapter construct can be classi ed in two main groups:

main.tex; 12/01/2000; 16:02; p.23

24 import pricing ; import summing; import product ; abstract adapter Total Pricing f abstract adapter Pricing Composite extends Composite f g abstract adapter LineItemRole Element adapts LineItemRole extends Element f double value () f return price ( ); g g g abstract adapter Total Pricing Product extends Total Pricing f adapter Order Pricing Composite adapts Order extends Pricing Composite f Elements [ ] elements () f return quotes (); g g adapter Quote LineItemRole Element adapts Quote extends LineItemRole Element f /  as in Pricing Product in Fig . 14  / g adapter HWProduct ItemRole adapts HWProduct extends ItemRole f /  as in Pricing Product in Fig . 14  / g ... g adapter Reg Total Pricing Product extends Total Pricing Product f /  as in Reg Pricing Product in Fig 14  / g Figure 18. Integration of Summing with Pricing and Product (2)

(a) Global scope: The modi cations speci ed by the adapter are globally visible in the sense that after compiling adapter A f ... adapter R adapts C1 .B extends C2 .S adaptation body ... g only an in-place modi ed B is visible with the new de nition being equivalent to class B extends S f B-def adaptation bodyg. If only single inheritance is supported and B already uses the sole inheritance link, one should think of the resulting de nition of B as being equivalent to class B f B defs S defs adaptation bodyg, i.e., S's de nition after attening its inheritance chain gets "copied" within B. This is the generative case that applies the adapter statically, i.e., at adapter compilation time or during class loading. Either source code generators or binary component adaptation tools [6, 3] can be used for this purpose. The client's use of the integrated functionality is as in the sample code for the pricing example in Fig. 19. Note the regular- pre x of the price() method. This is because we assume a code generation scheme that uses adapter names to qualify method names for avoiding name clashes.

main.tex; 12/01/2000; 16:02; p.24

25 public class Client { static public void main(String[] args) { Quote q = new Quote(); System.out.println(q.regular_price()); } }

Figure 19. Using a Globally Scoped Regular Pricing

The primary advantage of static, invasive integration is eciency, as it will avoid the need for excessive delegation to the base object. Actually, there are no adapter{adaptee pairs and the functor role of the composite adapter instances is no longer needed; composite adapters do not exist during runtime. The primary disadvantage is loss of exibility. Enforcing the component adaptation to occur at either compile or load time restricts a runtime object from acquiring the services of new components that become available after its instantiation. Thus only anticipated collaborations are supported. This also contrasts the Java philosophy of loading program constructs as they are needed, and not before. Since the base component's classes are modi ed inplace, all class instances are a ected. Thus, it is not possible to be selective as to which objects acquire the new class semantics. Furthermore, objects become very heavy, in that they must support the state required for all of the possible collaborations in which they might participate. (b) Local scope The adapter introduces a new name space, which means that after the composite adapter is compiled both the original B as in the component C1 and its role in the collaboration de ned by the composite adapter, A.R will co-exist. Two subcases can be distinguished here: 1. Transparent scope: Given adapter R adapts B extends S adaptation body de ned within the composite adapter A, we have that A.R is substitutable for B. This would be e.g., the case if A.R is generated as class A.R extends S f B-defs adaptation-code g. That means, if b is an instance of B and a an instance of A, than (b liftTo a) provides the original interface of B plus the interface of A.R. 2. Opaque scope: A.R is a "view" over B, meaning that for object b we can call methods in the original interface of B, while the result of (b liftTo a) provides only the interface of A.R. The composite adapter pattern presented in [16] (the structure of which was shown in Sec. 2) provides (Java) programmers with a language idiom to simulate the b.2 version of the adapter construct. A simulation of adapters with transparent scope (b.1) requires extended object models supporting objectbased inheritance [11] as e.g., Self [24] or the Darwin model and its Java based realization called Lava presented in [7]. Assuming Lava as the underlying language, modifying the technique in [16] to support transparent scope essentially

main.tex; 12/01/2000; 16:02; p.25

26 means replacing the adaptee relation between role and base objects in Fig. 6, i.e., forwarding semantics, with a delegatee relation as de ned in Darwin, i.e., true delegation semantics with late-binding of self. The rest of the pattern remains the same. Recall though that the pattern presented in [16] is too low-level for the average programmer to manage. For experimental purposes, we have developed a more abstract version of the technique presented in [16] that makes use of the re ective facilities of the Java 2 platform. Brie y, the technique is as follows. The programmer de nes all composite adapter classes as direct or indirect subclasses of a prede ned class CompositeAdapter. On the rst call of its constructor a CompositeAdapter makes use of the re ective API of Java 2 { namely of the new method getDeclaredClasses() implemented in java.lang.Class { in order to automatically establish the infrastructure of adapter factories (cf. Fig. 6) as well the infrastructure needed for managing the adapter{adaptee relations. In this way the work of the programmer is facilitated, in that he/she (a) does not need to implement the AdapterObject interface from Fig. 6 for each inner class adapter, and (b) does not need to take care of de ning and initializing the factory objects for each class adapter. However, he/she still needs to explicitly call lifting/lowering operations at the appropriate places. Further details about this realization are out of the scope of this paper. The interested reader can nd more information and sample code for the examples in the paper in [14]. The prototype outlined above allows us to experiment with the adapter within the context of a mainstream language such as Java. But it still remains complex for an unexperienced programmer. In the long run, a preprocessor will be needed to allow the programmer to work with the high-level adapter construct presented in this paper, while having the preprocessor statically transform adapter bodies to generate the corresponding composite adapter class as it would be hand-written by the programmer, if he/she used the pattern described in [16].

5. Related Work VanHilst and Notkin propose an approach for modeling collaborations based on templates and mixins as an alternative to using frameworks [20]. However, this approach may result in complex parameterizations and scalability problems. Smaragdakis and Batory solve this by elevating the concept of a mixin to multiple class granularity, using C++ parameterized nested classes [17]. However, their approach does not address the issue of dynamic customizations as described by Holland [9]. A Contract [9] allows multiple, potentially con icting component customizations to exist in a single application. However, contracts do not allow con icting customizations to be simultaneously active. Thus, it is not possible to allow di erent instances of a class to follow di erent collaboration schemes. Seiter et al. proposed a context relation to link the static and dynamic aspects of a class [18]. While supporting multiple dynamic collaboration schemes,

main.tex; 12/01/2000; 16:02; p.26

27 the approach is based on dynamically altering a class de nition for the duration of a method invocation, thus a ecting all class instances. Multiple dynamic variations of an object's behavior are also supported in the Rondo model [13]. However, Rondo does not provide explicit support for collaborations. In this paper we propose a model for scoping the di erent collaboration schemes, thus we can be selective as to which objects are a ected. Batory proposed the GenVoca architecture to de ne parameterized, plugcompatible, interchangeable and interoperable components [1]. The GenVoca model is based on the notion of realm, interface, component and layer. Layers represent encapsulations of composite-object decorators, which could be dynamically composed. The technique we have presented can be used as an elegant Java implementation for GenVoca layers. Einarson and Hedin also suggest the use of inner classes as alternative implementations of several design patterns [4]. Mattson et al. [12] also indicate the problems with framework composition, analyze reasons for these problems, and investigate the state of the art of available solutions. Bosch argues that language support should be provided for explicitly describing design patterns in object-oriented programs [2]. Among supporting other patterns, he also provides a language construct for specifying a class as the adapter of another class, i.e., for explicit expression of the adapter pattern [5]. The adapter construct as proposed in [2] has two main restrictions. First, it does not support adaptation of entire collaborative functionality. Second, as indicated in [2], it does not allow interface incompatibility. An underlying theme of the work described in this paper is separation of concerns to avoid software tangling. This is also the motivation behind both Aspect-Oriented Programming [25] and HyperSpaces (a new model of subjectoriented programming) [19]. AspectJ [25] is an extension of Java that allows one to program di erent aspects separately. Mezini and Lieberherr proposed Adaptive Plug and Play Components, or AP&PCs, which de ne a slice of behavior for a set of classes, and can be parameterized to allow reuse with di erent class models. An enhanced form of AP&PCs that decreases tangling of connectors and aspects in AspectJ is described in [10]. This improved form of AP&PC uses similar techniques as described in this paper, along with tool support.

Summary and Future Work This paper studied traditional framework customization techniques and concluded that they are inappropriate for component-based programming since they lack support for non-invasive, encapsulated, dynamic customization. We proposed a new language construct, called pluggable composite adapter, for expressing component gluing explicitly and discussed alternative realizations of the construct. The construct allows the separation of customization code from application and framework implementations, resulting in better modularity, hence, in more exible extensibility, easier maintenance and understandability.

main.tex; 12/01/2000; 16:02; p.27

28 As described in this paper, the adapter construct is focused only on a special kind of integration: additive integration. The adapter-based integration allows dynamic, noninvasive extension of a base component with additional behavior. In the more general case, the integration has overriding rather than additive semantics, i.e., it will cause the modi cation of existing behavior in at least one of the components being integrated. Weaving aspects as they are conceived in the aspect-oriented extension of Java, Aspect/J [25], has an overriding nature. Although overriding semantics are not supported by the version of the adapters presented in this paper, there is nothing inherent that would prevent an appropriate extension of the adapter construct to serve this need. In fact, as preliminary work in that direction shows [10], adapters seem to provide an appropriate mean for decreasing tangling between aspect de nitions and weaving concerns present in Aspect/J, providing for more reusable aspects. This research track is one of our very near goals in the future. We are also working on a denotational semantics of the pluggable composite adapter construct. An investigation of how adapters play together with fundamental component technology such as the EJB model, Java's servlets, etc. will be an interesting area of future work. Last but not least, we intend to demonstrate the usefulness of the pluggable composite adapter construct by benchmarking it using real-life applications.

Acknowledgements We would like to thank Sonali Kochar, David Lorenz, Doug Orleans, and Johan Ovlinger for their feedback on this paper.

Notes The de nition of this component is rst described in [9]. The technique has been originally formulated for framework-application integration, but it can be easily extended to serve the case when two fully-implemented components are to be integrated (replacing the inheritance links by usage links). 1 2

References 1. Don Batory and Vivek Singhal and Je Thomas and Sankar Dasari and Bart Geraci and Marty Sirkin The GenVoca Model of Software-System Generators. In IEEE Software, 11(5), 1994. 2. J. Bosch. Design Patterns as Language Constructs. In Journal of Object-Oriented Programming, 1998. 3. G. Cohen, J. Chase, and D. Kaminsky. Automatic Program Transformation with JOIE. In USENIX 1998 Annual Technical Conference, pp. 167{178, 1998. 4. D. Einarson and G. Hedin. Using Inner Classes in Design Patterns. Available at . 5. E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.

main.tex; 12/01/2000; 16:02; p.28

29 6. U. Holzle and R. Keller. Binary Component Adaptation. In Proceedings of ECOOP '98, Springer Verlag LNCS 1445, pp. 307{329, 1998. 7. G. Kniesel. Type-Safe Delegation for Run-Time Component Adaptation. In Procedings of ECOOP '99, Springer Verlag LNCS 1628, pp. 351{366, 1999. 8. J. Gil and D. Lorenz. Design Patterns and Language Design. In IEEE Computer, 31(3), pp. 118{120, 1998. 9. I. Holland. The Design and Representation of Object-Oriented Components. Ph.D. Dissertation, Northeastern University, Computer Science, 1993. 10. K. Lieberherr, D. Lorenz, and M. Mezini. Modeling Aspects with Adaptive Plug & Play Components. College of Computer Science, Northeastern University, Technical Report No. NU-CCS-99-01, Boston, MA, 1999. 11. H. Lieberman. Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems. In Proceedings of OOPSLA '86, ACM Sigplan Notices, 21(11), pp. 214{223, 1986. 12. M. Mattson and J. Bosch and M. Fayad. Framework Integration Problems, Causes, Solutions. Communications of ACM, 42(10), pp. 80-87, 1999. 13. M. Mezini. Variational Object-Oriented Programming Beyond Classes and Inheritance. Kluwer Academic Publishers, 1998. 14. M. Mezini. A Re ective Implementation of Pluggable Composite Adapters. http://www.informatik.uni-siegen.de/ mira/DynCompGlue.html 15. M. Mezini and K. Lieberherr. Adaptive Plug and Play Components for Evolutionary Software Development. In Proceedings of OOPSLA '98, ACM Sigplan Notices, 33(10), pp. 97{116, 1998. 16. L. Seiter, M. Mezini, K. Lieberherr. Dynamic Component Gluing in Java. In Proceedings of 1st Symposium on Generative and Component-Based Software Engineering (GCSE '99), Springer Verlag, LNCS, 1999. 17. Y. Smaragdakis and D. Batory. Implementing Layered Designs with Mixin Layers. In Proceedings of ECOOP'98, Springer Verlag LNCS, pp. 550-570, 1998. 18. L. Seiter, J. Palsberg, and K. Lieberherr. Evolution of Object Behavior using Context Relations. In IEEE Transactions on Software Engineering, 24(1), pp. 79{92, 1998. 19. P. Tarr, H. Ossher, W. Harrison, S. Sutton Jr. N Degrees of Separation: MultiDimensional Separation of Concerns. In Proceedings of ICSE'99, Los Angeles, pp. 107{119, 1999, ACM Press. 20. M. VanHilst and D. Notkin. Using Role Components to Implement CollaborationBased Designs. In Proceedings of OOPLSA'96, ACM Sigplan Notices, 31(10), pp. 359{369, 1996, San Jose. 21. IBM San Francisco Technical Summary. http://www.software.ibm.com/ad/sanfrancisco/about.html 22. R. Monson-Haefel. Enterprise Java Beans. O'Reilly & Associates, Inc., 1999. . 23. C. Szyperski. Component Software: Beyond Object-Oriented Programming. AddisonWesley, 1998. 24. D. Ungar and R. Smith. Self: The Power of Simplicity. In Proceedings of OOPSLA '87, ACM Sigplan Notices, 22(12), pp. 227{242, 1987. 25. Xerox PARC AspectJ Team. AspectJ, Xerox PARC Technical Report, January 1999. http://www.parc.xerox.com/spl/projects/aop/

main.tex; 12/01/2000; 16:02; p.29

main.tex; 12/01/2000; 16:02; p.30

Suggest Documents