A Transformational Process-Based Formal Approach to Object-Oriented Design P.S.C. Alencar, D.D. Cowan, J. Dong and C.J.P. Lucena Department of Computer Science University of Waterloo, Waterloo, Ont, Canada N2L 3G1 falencar, dcowan, jdong,
[email protected] Abstract
This paper presents a formal transformational process-based approach to object-oriented design. This approach uses a process language that allows us to represent the changes of the object models through the design process and, in particular, the changes induced by the application of design patterns. The approach is illustrated through a small example dealing with expression trees that is designed based on three design patterns: the bridge, the abstract factory, and the adapter. In this case, we provide an implementation in Prolog that allows us to obtain the object model design of the example by the application of some transformations in a structured semi-automated manner. The processes of this language can be formally de ned as schema transformations of theory presentations associated with the object models represented in a particular object calculus. We believe the method scales up to larger systems as it allows architectural object-oriented transformations to be also de ned.
Keywords: Software process, software transformations, object-oriented design, design patterns, formal semantics, formal methods, Prolog.
1 Introduction The object-oriented software development process is gradually achieving more rigor. Formal object-oriented languages exist which support the creation of complete, consistent, and unambiguous object speci cations. These formal languages combine the bene ts of precise mathematical notations with the advantages of object-oriented structuring mechanisms. Languages in this eld include the object-oriented version of VDM [18, 19] called VDM++ [14], Object-Z [13], and Z++ [20]. On the other hand, object-oriented software developers now tend to construct software indirectly through the abstraction of models. These system models are built using object-oriented methods that provide intuitive graphics and user-friendly languages and allow the developer to work at a level of abstraction that helps him to concentrate on the essential aspects of the the system. Approaches to object-oriented requirement analysis and design include [6, 32, 27, 9, 10, 7, 8]. The Object Modeling Technique (OMT) [27], for example, provides three types of models: object models, functional models, and dynamic models. The use of these three notations in a complementary manner allows the system developer to express and re ne system requirements into a design and implementation. Although each of these models contributes to the understanding of a system, the object model is of a central importance. The object model de nes the static structure of the elements of a system. Essentially, this models determines the types of objects that can exist in the system and identi es allowable relationships among objects. However, the lack of precise de nitions for these models makes it dicult to combine this approach with rigorous, systematic software development methods. The work described here has been supported by the Natural Sciences and Engineering Research Council of Canada (NSERC), the Information Technology Research Centre of Canada (ITRC), the National Research Council of Brazil (CNPq), and WATCOM.
1
When these object-oriented methods are adopted, the developers work the system requirements and design in the level of abstraction provided by their associated models, and the development process is essentially based on the expression and re nement of the system models. The process of changing and re ning this models can be thought of as a transformational process. The design process, according to the view presented in this paper, consists of transforming the object models in order to re ne and optimize them. We also advocate that these transformations should represent changes not only related to data and methods, but should also explicitly deal with good object-oriented design practice and its related processes. In this sense, the transformations extend the power of these models as the developers work in the level of the models and do not have to deal with tedious manipulation of source code. Furthermore, if these transformations are de ned in a formal way we can also have the bene ts of having both formal models to express the object models and formal mechanisms to transform these models. This paper presents a formal transformational process-based approach to object-oriented design. This approach uses a process language that allows us to represent the changes of the object models through the design process. The approach is illustrated through a small example dealing with expression trees which design can be based on three design patterns: the bridge, the abstract factory, and the adapter. In this case, we provide an implementation in Prolog that allows us to obtain the design of the simple application object model by the application of some transformations in a structured semi-automated manner. The processes of this language are formally de ned as schema transformations of theory presentations associated with the object models represented in a particular object calculus. We believe the method scales up to larger systems as it allows the de nition of architectural object-oriented transformations.
2 Software Transformations and Object-Oriented Design The transformations approach is a constructive approach in which a speci cation or a program is derived by successive application of (correctness-preserving) transformations that lead to re ned or optimized versions of speci cations or programs and, particularly, to a correct implementation of the program [25]. Transformations have been proposed and applied in several areas of software engineering, including forward and reverse engineering, schema integration, and schema evolution. Transformations in general can be essentially classi ed into various types: equivalence, information-losing, or information-gaining. They can also be applied of the same level of abstraction (horizontal transformations), and between dierent levels of abstraction (vertical transformations). Transformations related to forward engineering are in general information-gaining, while the transformations related to reverse engineering are information-losing. Many transformation systems that manipulate programs or speci cations to change their descriptions have also been built [30, 31, 5, 12, 23, 15, 22]. However, although ten years ago the theoretical background of the transformational methodology was proposed [26], and although some informal work has been done in the area of object-oriented transformations [4], there is still a lack of a theoretical base for object-oriented transformations. Although an initial formalization was provided in [2], in this paper we view the design process as the process of transforming the object-oriented design models in a general way. Each transformational process contributes to transform the object model in a certain way. We will concentrate, however, on transformations induced by design patterns.
2.1 Design Patterns Design patterns [16, 11, 6] describe simple and elegant solutions to speci c problems in object-oriented design. Patterns describe components and their interconnections. Patterns are normally viewed as recipes for interconnection; they are similar to cooking recipes in that they combine ingredients (components) into a nished product and the recipe no longer exists once the product is complete. Recipes and patterns can also be viewed as transformers in that they often take an ingredient and transform that ingredient into a new form. A pattern is a process applied to a number of components and the result is an application of the pattern. Patterns are both processes and objects themselves, since they can be represented in a language that can be manipulated. Current work on patterns has concentrated on discovering new ones and nding stylized but informalapproaches to documentation. The process of applying patterns can be time-consuming and is potentially fraught with errors. If we wish to apply patterns consistently and develop tools to assist in the application of patterns, we must nd
a formal approach to description. An initial approach to the formalization of the application of design patterns was presented in [2, 3]. Formalization of a pattern requires two components: an interconnection structure or matrix into which we t the various objects, and a clear description of the process of placing the objects in the matrix. Formal description of pattern applications does not impinge on the creative aspects of design, rather it just clari es the mechanical aspects of applying design techniques once they have been discovered and codi ed. Tools to assist the designer can be of signi cant help, the designer chooses the pattern, but the tools then apply it without error. Thus, tools for applying patterns will not only help to produce more error-free code, but because most of the labour of applying a pattern has been automated, experimentation is encouraged. As an example of a pattern, see the description of a Bridge [16] in the OMT diagram of Figure 1. The purpose of a Bridge is to decouple an abstraction from its implementation so that the two can vary independently Abstraction Operation() o
imp
Implementor
imp->OperationImp()
RefinedAbstraction
OperationImp()
ConcreteImplementorA
ConcreteImplementorB
OperationImp()
OperationImp()
Figure 1: Bridge Pattern
2.2 Overview of the Approach We provide a process language [1] that allows us to change the object model design schema, and in particular to describe the changes produced by design pattern applications. The introduction of this process language has many advantages. The process language can be a basis for representing the abstract and structural changes and transformations induced by the patterns. In this case, the object-oriented speci cations are represented by schemas that are transformed by the processes. It can also produce descriptions that can be translated in a natural way to programming logic languages such as Prolog so that we can take advantage of their deductive and query facilities in order to manipulate, transform, change, extend, and recover design pattern structures. The feasibility of using those facilities and their power to deal with extensive data have been demonstrated for example, by work related to genome-mapping logical databases [17]. There are also initial attempts to recover object-oriented design patterns in existing software systems [29]. Furthermore, the process language leads to a better characterization of the various sub-design levels that appear when we are developing software based on design patterns. In a lower level we use primitive components. Higher level components can be formed by putting the primitive components together by various kinds of pattern-based processes. Furthermore, a process level characterization for the patterns may allow us to build tools to translate the process language descriptions into an object-oriented programming language. For example, Bridge pattern application can be described by the process language as follow: Name:
BRIDGE(Abstraction, Implementor, imp, RefinedAbstractionSet, ConcreteImplementorSet, impOperation, Operation)
Stable:
Abstraction, Implementor
Structure: AGGREGATE(Abstraction, Implementor, imp, ConcreteImplementorSet, impOperation, Operation) set(RefinedAbstractionSet) for all element(RefinedAbstraction, RefinedAbstractionSet) { inherit(Abstraction, RefinedAbstraction) class(RefinedAbstraction)
} Unstable:
RefinedAbstractionSet
Extension: for all NewRefinedAbstraction { inherit(Abstraction, NewRefinedAbstraction) class(NewRefinedAbstraction) }
The overview of our approach is shown as Figure 2. The design pattern applications can be described by our process language, and then translated to Prolog rules and facts. We could also generate code both from our process language and from Prolog. The dashed line represents the normal path from design to implementation without our process language and Prolog. The reverse direction shows how to recover design patterns from source code if CASE tools could be built to extract necessary facts in Prolog from the source code. Design Pattern Applications
Process Language
Prolog
Code
Figure 2: Overview of Our Approach
3 Process Language The process language [1] for describing design pattern applications is implementation language independent. Our goal is to describe the context of design pattern application as much as possible without conforming to any kind of object-oriented languages, such as C++, Java and Smalltalk. We try to capture the general primitives of object-oriented languages in the process language. These primitives are basic building blocks of our descriptions of design pattern applications. In general, the structure of the process language has ve essential elements: Name: We follow the convention of the GOF book [16] on pattern names. Each name introduces the problem and context of a design pattern. The following elements of the process language structure concentrate on the descriptions of the design pattern applications. Each pattern name is followed by parameters which specify the parts that participate in this pattern so that we can use the pattern name with its parameters as a building part in the description of another pattern. Stable: The relevant stable parts (classes or set of classes) in the design pattern application. The structure of these parts will not be changed by the extension of the design pattern applications. Structure: This entry describes the solution of each pattern using the primitive operators and the pattern primitives provided by the process language. It describes each part of the design pattern applications, their relationships and collaborations. In some cases, the collaboration among the parts can be described by state variables that point to other parts. Unstable: The unstable parts (classes or set of classes) in the design pattern application. The change of these parts in order to adapt to the new requirements is the main operation to extend the design pattern applications. Extension: It tells us how to keep the stable parts intact and modify the unstable parts to achieve reuse. It describes how the design pattern applications can evolve. This is one of the purposes for the introduction
of design patterns. Pattern structures can be extended in two levels. In the rst level, some pattern substructures that we call pattern primitives are de ned by the primitive operators of the process language. One example of a pattern primitive is the sub-structure that represents the way in which some objects are put together as an aggregate. This sub-structure can be used in the de nition of pattern applications. In the second level, we de ne some processes that describe how the pattern applications can evolve. For example, we can make the Bridge pattern application evolve by a process that takes care of the inclusion of another Re nedAbstraction class in the Bridge structure. Please see the description in section 2.2. The structural part of each pattern can be described by the following primitive operators and statements. The primitive operators are the basic units of our process language. set(ClassSet): ClassSet is a set of classes. Normally they have the same property such as the same parent class. element(A, B): A is an element of set B. class(A)f...g: A is a class name with some de nitions in f...g abstractclass(A)f...g: A is an abstract class name with some de nitions in f...g inherit(A, B): B is a subclass of A. variable(A, P, a, t): a is a variable in class A with type t. t is optional. P describes the access rights of this variable, i.e. public, private, and protected. method(A, P, f): f is a function method in class A. P describes the access rights of this method, i.e. public, private, and protected. code(f, \..."): There are some suggested pseudo-code which should be included in the method f. parameter(A, f, p): p is a parameter of function method f in class A. overwrite(A, f): f is a function method in class A that overwrites its parent class. We use a statement for universal quanti cation for all in the process language. It enumerates all the elements in a set. So for all element(e, S) f...g means 8 e2S, f...g. For example: for all element(ConcreteAggregatee, ConcreteAggregateeSet) { inherit(Aggregatee, ConcreteAggregatee) }
means 8 ConcreteAggregatee 2 ConcreteAggregateeSet, inherit(Aggregatee, ConcreteAggregatee) . The comment statement # improves readability of the process language. Statement Explanation for all element(e,S) f...g for all e that is an element of Set S do f...g # Any characters after \#" on a line are comments. In order to achieve a higher level of abstraction, we provide pattern primitive operators which is constructed by primitive operators in the process language. The primitive operators are the ones that should be used to construct object-oriented object schemas in general, such as aggregation, delegation, specialization, and association. Pattern primitive operators are constructed by primitive operators, which capture the sub-patterns occurring frequently in the design pattern solutions. Pattern primitive operators are operators that change or transform the pattern speci cation schemas and take advantage of the fact that patterns also cope with evolution. For example, one of these operators can add the necessary extra structure needed if one more product is to be added in the case of an abstract factory pattern. This operator can assist with the evolution of the pattern schema and also with the application of this pattern. As an example of a pattern primitive operator, see the OMT diagram (Figure 3) and the description of aggregation in the process language below:
aggregater
Aggregater Operation
o
Aggregatee AggregateOperation
aggregater aggregater->AggregateOperation()
ConcreteAggregatee1 AggregateOperation
ConcreteAggregatee2 AggregateOperation
Figure 3: Aggregation Name: AGGREGATE(Aggregater, Aggregatee, aggregater, ConcreteAggregateeSet, AggregateOperation, Operation) Stable:
Aggregater, Aggregatee
Structure: #Aggregatee# abstractclass(Aggregatee) { method(Aggregatee, public, AggregateOperation) } set(ConcreteAggregateeSet) for all element(ConcreteAggregatee, ConcreteAggregateeSet) { inherit(Aggregatee, ConcreteAggregatee) class(ConcreteAggregatee) { method(ConcreteAggregatee, public, AggregateOperation) } } Aggregater # abstractclass(Aggregater) { variable(Aggregater, private, aggregater, Aggregatee) method(Aggregater, public, Operation) code(Operation, "aggregater -> AggregateOperation()") } Unstable:
ConcreteAggregateeSet
Extension: for all NewConcreteAggregatee { inherit(Aggregatee, NewConcreteAggregatee) class(NewConcreteAggregatee) { method(NewConcreteAggregatee, public, AggregateOperation) } }
The description of a design pattern application can also be viewed as a transformation from one architecture
to another architecture. For example, the application of the Adapter(Class) pattern [16] can be described as a sequence of transformations: rst, the basic Class A is transformed into another structure in which a class Class B inherits from the Class A; and then a second transformation takes care of the generation of a new structure by the introduction of another class Class C, and the addition of some new functionality in Class B to make it inherit from Class C as well (see Figure 4). The transformations are achieved by the rst-order logic predicates that characterize the process language. A
A
A
C
B
B
Figure 4: Transformation of Structures
3.1 A Possible Semantics for the Transformational Processes As we have mentioned in Section 2, there is still a lack of a theoretical base for object-oriented transformations [4]. A possible approach to the semantics of the transformational processes presented in the previous section is outlined in what follows. Essentially we have to adapt the results presented in [26, 24] to a particular object-oriented formal speci cation language. We can use the object calculus adopted in [2] or the one related to VDM++ [21]. In any case, an objectoriented formal speci cation is a well-formed expression over the signature of the adopted object calculus. The semantics of a formal speci cation is de ned by the mapping or interpretation that associates a set of semantic values to each formal speci cation. A speci cation schema is an expression over containing free variables from a countable set of (sorted) schema parameters. Speci cation schemas are a generalization of speci cations; speci cations are speci cation schemas that do not contain free variables. Instances of speci cation schemas are nite partial mappings from the domain of the schema variables to speci cation schemas. For a speci cation schema and an instance , the instantiation of with is obtained by simultaneously substituting all schema parameters in by speci cation schemas according to . Transformation rules are special inferences of the form ( ), where is a set of applicability conditions and is a binary semantic predicate (read as `transformable') over speci cation schemas that, i.e. `equivalent' or `descendant'. and are also called input schema and output schema respectively. The application of a transformation rule ( ) to a speci cation schema is de ned to result in a new inference ( ; ), where is determined by matching the input speci cation schema against , denotes the instantiation of with , and of with . Within the calculus sketched previously, each transformational development amounts to deriving a new inference. An additional way of deriving new inferences from available ones is provided by the mechanism of meta-inferences. The details about this possible semantics for the transformational processes described in the previous section are the subject of a forthcoming paper. SL
SL
X
s
s
s
s
C = REL I ;
O
C
REL
I
O
C = REL I ;
C
C
= REL t;
O
t
O
I
C
O
t
O
3.2 Implementation in Prolog This process language can be implemented in programming logic languages such as Prolog. As an example, the implementation of aggregate pattern primitive is shown as follows: forall([], Aggregatee, AggregateOperation). forall([ConcreteAggregatee|ConcreteAggregateeSet], Aggregatee, AggregateOperation) :-
assert(inherit(Aggregatee, ConcreteAggregatee)), assert(class(ConcreteAggregatee)), assert(method(ConcreteAggregatee, pub, AggregateOperation)), forall(ConcreteAggregateeSet, Aggregatee, AggregateOperation). aggregate(Aggregater, Aggregatee, Aggregate, ConcreteAggregateeSet, AggregateOperation, Operation) :assert(abstractclass(Aggregatee)), assert(method(Aggregatee, pub, AggregateOperation)), forall(ConcreteAggregateeSet, Aggregatee, AggregateOperation), assert(abstractclass(Aggregater)), assert(variable(Aggregater, private, Aggregate, Aggregatee)), assert(method(Aggregater, pub, Operation)), assert(code(Operation, Aggregate, '->', AggregateOperation, '()')). extend_aggregate(NewConcreteAggregateeSet, Aggregatee, AggregateOperation) :forall(NewConcreteAggregateeSet, Aggregatee, AggregateOperation).
The aggregate predicate takes in some parameters. It writes some facts that are the components of aggregate into the Prolog database. The forall predicate is for universal quanti cation for all in our process language. It quanti es a set of classes to write the facts of each class into the Prolog database. The extend aggregate predicate tells us how to extend the aggregate. The description of aggregate pattern primitive in our process language which consists a set of primitive operators is described previously. The primitive operators (predicates) are easily represented in Prolog. Taking advantage of the facilities of Prolog, we can design software at a higher level of abstraction. We can achieve component-based design by applying the rules which represent the design patterns in Prolog so that we can get new facts which represent the new components of the system. As well as adding new components into a system, we can also take away components by the retract facility in Prolog. The description (see section 1) of Bridge pattern application in our process language includes the description of aggregate pattern primitive as a component. It can also apply the rule of aggregate as a component of Bridge pattern application in Prolog, which is shown as follows: bridge(Abstraction, Implementor, Imp, RefinedAbstractionSet, ConcreteImplementorSet, ImpOperation, Operation) :aggregate(Abstraction, Implementor, Imp, ConcreteImplementorSet, ImpOperation, Operation), forall_bridge(RefinedAbstractionSet, Abstraction). extend_bridge(NewRefinedAbstractionSet, Abstraction) :forall_bridge(NewRefinedAbstractionSet, Abstraction). forall_bridge([], Abstraction). forall_bridge([RefinedAbstraction|RefinedAbstractionSet], Abstraction) :assert(inherit(Abstraction, RefinedAbstraction)), assert(class(RefinedAbstraction)), forall_bridge(RefinedAbstractionSet, Abstraction). retract_bridge(OldRefinedAbstractionSet, Abstraction) :forall_retract_bridge(OldRefinedAbstractionSet, Abstraction). forall_retract_bridge([], Abstraction). forall_retract_bridge([RefinedAbstraction|RefinedAbstractionSet], Abstraction) :retract(inherit(Abstraction, RefinedAbstraction)),
retract(class(RefinedAbstraction)), forall_retract_bridge(RefinedAbstractionSet, Abstraction).
The bridge predicate takes in some necessary parameters and applies the aggregate predicate which writes all the facts with the parameters that the bridge predicate passed into the Prolog database. It then calls universal quanti cation (forall bridge predicate) to quantify a set of Re nedAbstraction classes. This is an example of how we achieve the component-based design in a formal way. The Bridge pattern can be built by one big component (aggregate) with some small components (primitive operators in our process language or facts in Prolog). That means we can describe how to put things together by dierent granular components into a product. The extend bridge predicate gives us a direction on how to extend the Bridge pattern. Because some parts of the design patterns are relatively stable and some parts are unstable, we want to extend a pattern by keeping the stable parts unchanged and evolving the unstable parts. Design pattern applications give us directions on how to extend the patterns. We can describe how to extend a pattern in the Extend section of our process language. Meanwhile, we can also write a predicate in Prolog to achieve the extension of pattern application by adding facts into the Prolog database. We even can take out some components from the design structure by retracting the facts from the Prolog database, which is described by the retract bridge predicate. The extension and retraction are illustrations of schema transformation. These two predicates (extend bridge and retract bridge) are also used to transfer from one schema to another. The implementations of Abstract-Factory pattern and Adapter pattern in Prolog are shown as follows: Abstract-Factory abstract_factory(AbstractFactory, AbstractProduct, ConcreteFactorySet, ConcreteProductSet, Product) :assert(abstractclass(AbstractProduct)), forall(ConcreteProductSet, AbstractProduct), assert(abstractclass(AbstractFactory)), assert(variable(AbstractFactory, private, Product, AbstractProduct)), forall(ConcreteFactorySet, ConcreteProductSet, AbstractFactory, Product). forall([], AbstractProduct). forall([ConcreteProduct|ConcreteProductSet], AbstractProduct) assert(inherit(AbstractProduct, ConcreteProduct)), assert(class(ConcreteProduct)), forall(ConcreteProductSet, AbstractProduct).
:-
forall([], [], AbstractFactory, Product). forall([ConcreteFactory|ConcreteFactorySet], [ConcreteProduct|ConcreteProductSet], AbstractFactory, Product) :assert(inherit(AbstractFactory, ConcreteFactory)), assert(class(ConcreteFactory)), assert(method(ConcreteFactory, pub, ConcreteFactory)), assert(code(ConcreteFactory, Product, '=new', ConcreteProduct)), forall(ConcreteFactorySet, ConcreteProductSet, AbstractFactory, Product). extend_factory(NewConcreteFactory, NewConcreteProduct, AbstractFactory, AbstractProduct, Product) :assert(inherit(AbstractProduct, NewConcreteProduct)), assert(class(NewConcreteProduct)), assert(inherit(AbstractFactory, NewConcreteFactory)), assert(class(NewConcreteFactory)), assert(method(NewConcreteFactory, pub, NewConcreteFactory)), assert(code(NewConcreteFactory, Product, '=new', NewConcreteProduct)).
Adapter adapter(Adaptee, Adapter, Target, Adapt, Request, SpecificRequest) :assert(class(Adaptee)), assert(method(Adaptee, pub, SpecificRequest)), assert(abstractclass(Target)), assert(method(Target, pub, Request)), assert(parameter(Target, Request, Adapt, Adaptee)), assert(class(Adapter)), assert(inherit(Target, Adapter)), assert(method(Adapter, pub, Request)), assert(parameter(Adapter, Request, Adapt, Adaptee)), assert(code(Request, Adapt, '->', SpecificRequest)). extend_adapter(Adaptee, NewAdapter, NewTarget, Adapt, NewRequest, SpecificRequest) :assert(abstractclass(NewTarget)), assert(method(NewTarget, pub, NewRequest)), assert(parameter(NewTarget, NewRequest, Adapt, Adaptee)), assert(class(NewAdapter)), assert(inherit(NewTarget, NewAdapter)), assert(method(NewAdapter, pub, NewRequest)), assert(parameter(NewAdapter, NewRequest, Adapt, Adaptee)), assert(code(NewRequest, Adapt, '->', SpecificRequest)). retract_adapter(Adaptee, OldAdapter, OldTarget, Adapt, OldRequest, SpecificRequest) :retract(abstractclass(OldTarget)), retract(method(OldTarget, pub, OldRequest)), retract(parameter(OldTarget, OldRequest, Adapt, Adaptee)), retract(class(OldAdapter)), retract(inherit(OldTarget, OldAdapter)), retract(method(OldAdapter, pub, OldRequest)), retract(parameter(OldAdapter, OldRequest, Adapt, Adaptee)), retract(code(OldRequest, Adapt, '->', SpecificRequest)).
Further tools can be made to transfer the facts for a design in Prolog to code in object-oriented languages, such as C++, Java and Smalltalk as long as we have the BNF formal syntax speci cation for our process language. In our case the syntax speci cation is quite simple.
4 A Case Study 4.1 Description Of The Case Study Our approach is illustrated through a small example dealing with expression trees that is designed based on three design patterns: the bridge, the abstract factory, and the adapter [28]. Expression trees consist of nodes containing operators and operands. Operators have dierent precedence levels, dierent association, and dierent arities, e.g., multiplication takes precedence over addition. The multiplication operator has two arguments, whereas unary minus operator has only one. Operands are integers, doubles, variables, etc. (see Figure 5). The expression trees may be \evaluated" via dierent traversals, e.g. in-order, post-order, pre-order, levelorder. The evaluation step may perform various operations, e.g., traverse and print the expression tree, return the \value" of the expression tree, generate code, perform semantic analysis.
BINARY NODES
* +
UNARY NODE
5
3
4
INTEGER NODES
Figure 5: Expression Tree Diagram
4.2 Design Patterns There are three patterns which can be used in the design of the expression tree example: Adapter, AbstractFactory and Bridge.
Adapter
{ pattern name: Adapter { problem: Sometimes we want to reuse a toolkit class whose interface does not match the domainspeci c interface an application requires. { solution: Adapter pattern converts the interface of a class into another interface clients expect and lets classes work together that could not otherwise because of incompatible interface. Please see Figure 6 for the structures. Target Request()
SpecificRequest()
(implementation) Adapter Request() o
Adapter (Class)
Adaptee
Target
Adaptee
adaptee
Adapter
SpecificRequest()
SpecificRequest()
Request()
Request() o
adaptee->SpecificRequest()
Adapter (Object)
Figure 6: Adapter Pattern
{ consequence: Class and object adapters have dierent trade-os. A class adapter
1. adapts Adaptee to Target by committing to a concrete Adapter class. 2. lets Adapter override some of Adaptee's behavior, since Adapter is a subclass of Adaptee. 3. introduces only one object, and no additional pointer indirection is needed to get to the adaptee. An Object adapter 1. adapts Adaptee to Target by committing to a concrete Adapter class. 2. lets Adapter override some of Adaptee's behavior, since Adapter is a subclass of Adaptee. 3. introduces only one object, and no additional pointer indirection is needed to get to the adaptee.
4. lets a single Adapter work with many Adaptees. 5. makes it harder to override Adaptee behavior. Abstract-Factory { pattern name: Abstract-Factory { problem: Instantiating classes throughout the application makes it hard to change. A system should be independent of how its products are created, composed, and represented so that it can be con gured with one of multiple families of products. { solution: We can de ne an abstract factory class that declares an interface for creating each basic kind of product. There is also an abstract class for each kind of product, and concrete subclasses implement products for speci c requirements. The interface of abstract factory class has an operation that returns a new product object. Clients call these operations to obtain product instances, but clients are not aware of the concrete classes they are using. Please see Figure 7 for the structure of Abstract-Factory pattern. Client
AbstractFactory AbstractProductA CreateProductA() CreateProductB() ProductA2
ProductA1
AbstractProductB ConcreteFactory1 CreateProductA() CreateProductB()
ConcreteFactory2 CreateProductA() CreateProductB()
ProductB2
ProductB1
Figure 7: Abstract-Factory Pattern
{ consequence: The Abstract Factory pattern has the following bene ts and liabilities:
1. 2. 3. 4. Bridge
It isolates concrete classes. It makes exchanging product families easy. It promotes consistency among products. Supporting new kinds of products is dicult.
{ pattern name: Bridge { problem: When an abstraction can have one of several possible implementations, we want to avoid
a permanent binding between an abstraction and its implementation, i.e. make client code platformindependent. { solution: The Bridge pattern address these problems by putting the abstraction and its implementation in separate class hierarchies. All operations on the subclasses of abstraction are implemented in terms of abstract operation from the abstract implementation class interface. This decouples the abstractions from the various platform-speci c implementations. Please see Figure 1 for the structure of Bridge pattern. { consequence: The bridge pattern has the following consequences: 1. Decoupling interface and implementation. 2. Hiding implementation details from clients. 3. Improved extensibility.
4.3 Representations in the Process Language
Adapter Name:
ADAPTER(Adaptee, Adapter, Target, adaptee, Request, SpecificRequest)
Stable: Adaptee Structure: # Adpatee # class(Adaptee) { method(Adaptee, public, SpecificRequest) } # Target # abstractclass(Target) { method(Target, public, Request) parameter(Target, Request, adaptee, Adaptee) } # Adapter # class(Adapter) { inherit(Target, Adapter) method(Adapter, public, Request) parameter(Adapter, Request, adaptee, Adaptee) code(Request, "adaptee->SpecificRequest()") } Unstable: Adapter, Target Extension: for all NewTarget { # NewTarget # abstractclass(NewTarget) { method(NewTarget, public, NewRequest) parameter(NewTarget, NewRequest, adaptee, Adaptee) } # Adapter # class(NewAdapter) { inherit(NewTarget, NewAdapter) method(NewAdapter, public, NewRequest) parameter(NewAdapter, NewRequest, adaptee, Adaptee) code(NewRequest, "adaptee->SpecificRequest()") }
Abstract-Factory Name:
ABSTRACT-FACTORY(AbstractFactory, AbstractProduct, ConcreteFactorySet, ConcreteProductSet)
Stable: AbstractProduct, AbstractFactory Structure: # Product # abstractclass(AbstractProduct) set(ConcreteProductSet) for all element(ConcreteProduct, ConcreteProductSet) { inherit(AbstractProduct, ConcreteProduct) class(ConcreteProduct) } # Factory # abstractclass(AbstractFactory) { variable(AbstractFactory, private, product, AbstractProduct) } set(ConcreteFactorySet) for all element(ConcreteFactory, ConcreteFactorySet) { inherit(AbstractFactory, ConcreteFactory) class(ConcreteFactory) { method(ConcreteFactory, public, ConcreteFactory) code(ConcreteFactory, "product=new ConcreteProduct") } } Unstable: ConcreteFactorySet, ConcreteProductSet Extension: inherit(AbstractProduct, NewConcreteProduct) class(NewConcreteProduct) inherit(AbstractFactory, NewConcreteFactory) class(NewConcreteFactory) { method(NewConcreteFactory, public, NewConcreteFactory) code(NewConcreteFactory, "product=new NewConcreteProduct") }
Bridge Please see section 2.
4.4 Solution of the Expression Tree Start with Object-Oriented modeling of the \expression tree" problem domain, there are several classes involved: class Node: base class that describes expression tree vertices: { class Int Node: used for implicitly converting int to Tree node. { class Binary Node: handles binary operators. class Tree: \glue" code that describes expression tree edges. We can reduce the solution to the OMT structure diagram which contains two class hierarchies, Node and Tree (see Figure 8).
tree operator