The FAMOOS Object-Oriented Reengineering Handbook

insert() abstract delete() abstract lookup() abstract forall() HashSet . ListSet . Set impl insert() delete() lookup() forall() deleteAll() union() constructor() changeImpl() SetImpl abstract insert() abstract delete() abstract lookup() abstract forall() abstract changeImpl() constructor (type subtypeSetImpl) { impl = new type ; }insert() abstract delete() abstract lookup() abstract forall() abstract changeImpl() constructor (type subtypeSetImpl) { impl = new type ; } changeImpl (type subtypeSetImpl) { impl.changeImpl(type) ; } HashSet . changeImpl() {. } ListSet . changeImpl() {. } 3c 3b Figure 2.52: Application of Design Pattern Operator Bridge(II) 2.4. 1.2 Refactorings In his PhD thesis [J OHN 93], William Opdyke presented an approach for the support of the evolution of object-oriented systems. He defined a set of so-called refactorings, which are implementable in a tool and showed that the application of these refactorings does not change the system behaviour. Opdyke calls the property of not changing the behaviour of a program se antic equivalence and defines it as follows: Let the external interface to the program be via the function main. If the functionmainis called twice (once before and once after a refactoring) with the same set of inputs, the resulting set of output values must be the same. Besides the semantic equivalence, Opdyke additionally defines several more invariants to ensure the syntactic and semantic correctness of the refactorings. He does not, for example, allow the use of multiple inheritanceor theredefinition of inherited member variables . Every refactoring has to preserve these invariants. The refactorings are divided into two subsets: One subset contains 26 low-level refactoringsfocusing on simplicity in order to make it easy to prove that system behaviour is not changed. The other subset consists of threehigh-level refactorings , which are defined by using the low-level refactorings. The proof of the behaviour preserving nature of the high-level refactorings is also furnished in terms of low-level refactorings. Low-level refactorings allow the creation (e.g.,create empty class ), deletion (e.g., delete member function ) or modification of program entities (e.g. add function argument ). 2.4 Reorganisation(by B. Schulz) 95 Furthermore, refactorings to move member variables (e.g., move member variable to superclass ) and simple composite refactorings (e.g., convert code segment to function ) belong to the low-level refactorings. The description of these refactorings is as follows: Name: Denotes the name of the refactoring. Description: Describes the refactoring and its variants. Arguments: Enumerates the set of arguments and their meaning. Pre-conditions: Lists the set of pre-conditions, which have to hold in order to assure that the semantics of the program remain unchanged when applying the refactoring. Proof of correctness: A semi-formal proof that ensures that the refactoring does not change either the invariants or the semantics of the program, assuming that all pre-conditions hold. The three high-level refactorings described in [J OHN 93] are: Creation of an abstract superclass : The common abstraction (methods, member variables) of two or more classes is factored out into an abstract superclass. Subclassing and simplifying conditionals: Conditional statements, dependent on the internal state of the object, which are used to select the different behaviour of an object are replaced by an implementation using various subclasses and polymorphism. Converting an inheritance relation into an aggregation: A relation modelled by inheritance is converted into aggregation. The functionality, which was inherited can be accessed by delegating it to the component. The description of the high-level refactorings contains one additional section describing how it is decomposed into low-level refactorings, how the pre-conditions can be derived from them and how the proof of correctness is built upon the proof for the low-level refactorings. Since Opdyke presented the pre-conditions as well as the necessary restructuring in a detailed way, it was possible to construct tools for the support of the refactoring task. Due to the complexity of the C++ language15 Ralph Johnson and his group focused on Smalltalk and constructed a R factoring Browser for Smalltalk[ROBE 97b]. This tool supports a selection of the refactorings described in [J OHN 93] and was successfully used for the development of Hotdraw. Berthold Mohr showed in [MOHR 98] that it is possible to implement a tool supporting refactorings for a subset of C++. He implemented the refactoring for the conversion of an inheritance relationship into an aggregation including all necessary low-level refactorings. In spite of the promising results in formalising [J OHN 93] and implementing [ROBE 97b][MOHR 98] refactorings, there are some restrictions to the approach: Behaviour preservation: The described approach of assembling high-level refactorings from lowlevel refactorings requires behaviour preservation for every application of a low-level refactoring. It may be desirable to ignore this constraint and to guarantee behaviour preservation for the entire high-level refactoring only. Language dependance : It is not possible to specify all the pre-conditions in a language-independent way, because the semantics of a program heavily depend on the semantics of the underlying programming language. 15Opdyke writes in [JOHN 93]: “The C++ language is a semantically complicated language, supporting machine level operations such as pointer arithmetic; these complexities make it difficult to more precisely define what behaviour preservation means for C++ programs. ”