A Higher Abstraction Level Using First-Class Inheritance Relations.
-
Citations (0)
-
Cited In (0)
Page 1
A Higher Abstraction Level using First-Class
Inheritance Relations
Marko van Dooren and Eric Steegmans
Department of Computer Science,
K.U.Leuven
Marko.vanDooren@cs.kuleuven.be
Eric.Steegmans@cs.kuleuven.be
Abstract. Although classes are a fundamental concept in object-oriented pro-
gramming, a class itself cannot be built using general purpose classes as building
blocks in a practical manner. High-level concepts like associations, bounded val-
ues, graph structures, and infrastructure for event mechanisms which form the
foundation of a class cannot be reused conveniently as components for classes.
As a result, they are implemented over and over again.
We raise the abstraction level of the language with a code inheritance relation
for reusing general purpose classes as components for other classes. Features like
mass renaming, first-class relations, high-level dependencies, component param-
eters, and indirect inheritance ensure that maximal reuse can be achieved with
minimal effort.
A case study shows a reduction of the code between 21% and 36%, while the
closest competitor only reduces the size between 3% and 12%.
1Introduction
Although increasing the reusability of software is one of the main goals of object-
oriented software development, an important group of software elements still cannot
be reused in a practical manner. These elements are implemented over and over again,
resulting in massive code duplication and all its related problems.
A class often consists of application specific functionality written on top of general
purpose characteristics like associations, values lying within bounds, lockable values,
graph structures, and infrastructure for event listeners. Most of them are well-known
high-level concepts which are easy to use during the design phase. But during the im-
plementation phase, these concepts are transformed into low-level code because current
reuse mechanisms cannot cope with such reuse in a convenient manner.
Most reuse mechanisms [4,6,7,9,42,32,35,39] differ little from a regular inheri-
tance relation with subtyping and code inheritance. But the requirements for building
a class from components differ in important areas from those for creating a subtype.
Reusing a class as a building block for another class requires activities such as remov-
ing unwanted methods, wiring method dependencies, and especially renaming methods.
But for creating subtypes, the first activity is forbidden, the second one is not required,
and the third one is required only infrequently. In addition, methods of different build-
ing blocks are usually separated even if they have the same definition, while they are
usually merged in case of a multiple/repeated subtyping relation.
Page 2
Reuse mechanisms that focus on composition [26,36] create only shallow composi-
tions; the composition is just the sum of the parts. But a class is more than the sum of its
components; it adds application specific code and gives the components an application
specific meaning; it creates an abstract data type.
In this paper, we present an inheritance mechanism with two relations. The subtyp-
ing relation is used for traditional subtyping inheritance. The component relation allows
general purpose characteristics to be encapsulated in classes and be reused conveniently
as configurable building blocks for other classes. We analyze the requirements neces-
sary to realize this kind of reuse, and then introduce the required new features. We
introduce renaming parameters for mass renaming, and make the inheritance relation
first-class for accessing hidden functionality, treating components as separate objects,
and resolving method dependencies using high-level component connections. We eval-
uate the mechanism in a case study, where it is compared to existing approaches. We
also created a formal type system and proved the type soundness of the mechanism, but
due to space constraints, the formalization is not presented in this paper.
In Section 2, we analyze the requirements for the reuse mechanism, and discuss
existing mechanisms. In Section 3, we present the component relation, which is used
for code inheritance. In Section 4, we present the impact on the subtyping relation. We
evaluate the inheritance mechanism in Section 5 with an example and a case study. We
discuss related work and future work in Sections 6 and 7, and conclude in Section 8.
2Requirements Analysis
In this section, we analyze which features are required in order to conveniently reuse
general purpose classes as a building block for other class. We use a simple banking
application to illustrate the requirements.
In this paper, we illustrate the features of the inheritance mechanism mostly with
components for modeling associations, which use a simple protocol to keep the asso-
ciation consistent. The proposed inheritance mechanism, however, can reuse general
abstract data types – which can use arbitrarily complex protocols – as components.
Figure 1 illustrates the application. It contains classes for persons, bank accounts,
and bank cards. The rectangles inside a class represent its characteristics. For exam-
ple, an account has a balance, which is a number that lies between the credit limit and
an upper bound. In addition, it has a unidirectional association with its account num-
ber, and a bidirectional association with its owner. The associations for the parents and
children of a person form a graph offering different traversal strategies. Dependencies
between characteristics are represented by dashed arrows. For example, the owner and
accounts components need each other’s methods to keep the association consistent.
Figure 2 shows a Java implementation of class BankAccount. More advanced
functionality like sending events, and constraints on the associations is not shown.
The problem with the implementation is that it consists entirely of functionality that
has already been implemented millions of times before. Associations and constrained
values are common characteristics, and although the exact names of the methods and
the used types may differ, the behavior is always the same.
Page 3
Figure 1: High-level design of an application.
class BankAccount {
public BankAccount(int number) {
this.creditLimit = -1000;
this.upperLimit = 1000000;
this.accountNumber = number;
}
private Person owner;
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
if(this.owner != owner) {
registerOwner(owner);
if(owner != null)
owner.registerAccount(this);
}
}
protected void registerOwner(Person owner) {
if (this.owner != null)
this.owner.unregisterAccount();
this.owner = owner;
}
protected void unregisterOwner() {
owner = null;
}
private final int accountNumber;
public int getAccountNumber() {
return accountNumber;
}
private long balance;
private long upperLimit;
private long creditLimit;
public long getBalance() {
return balance;
}
public void deposit(long amt) {
if((amt > 0) &&
(balance<=Long.MAX VALUE-amt)
&&(balance + amt <= upperLimit))
balance += amt;
}
public void withdraw(long amt) {
if((amt > 0) &&
(balance>=Long.MIN VALUE+amt)
&&(balance - amt >= creditLimit))
balance -= amt;
}
public long getUpperLimit() {
return upperLimit;
}
public long getCreditLimit() {
return creditLimit;
}
}
Figure 2: The Java version of BankAccount.
Page 4
2.1Requirements
The goal is to construct a reuse mechanism that allows high-level concepts to be en-
capsulated and reused to build a class. The reusable entity is called a component. The
mechanism must minimize the effort required to reuse a component, and maximize the
reusability of its functionality.
The requirements1are illustrated using the example from Figures 1 and 2. They are
grouped to increase readability, but some features can be placed in multiple groups. We
omit features supported by all mechanisms, such as parameterized types.
Mandatory Features The following features are mandatory for building a class by
reusing components.
1. ADT Components: A component must contribute to the abstract data type of the
reusing class, which rules out a simple has-a relation. Otherwise, the composition
is too difficult to use. For example, the methods of Person would be spread over
several objects at different depths depending on the nesting of the components, and
have names that are almost meaningless in the context of the application.
2. Multiple Reuse: A class must be able to reuse code from more than one compo-
nent. For example, class BankAccount has three general characteristics.
3. Repeated Reuse: Because a class can reuse multiple components of the same kind,
it must be able to reuse a component more than once. For example, class Person
has three bidirectional associations.
4. Renaming: Renaming is required to solve name conflicts caused by repeated reuse,
give the reused methods a meaningful name in the context of the reusing class, and
merge features. Name conflicts will occur because components can be reused more
than once by a single class, as for example in class Person.
Expressivity Features These features reduce the amount of work needed to reuse
a component. The impact of a feature is shown using big O notation as activity :
Owithout→ Owith. It shows the amount of work required for an activity without and
with that feature when reusing a component. Note that the activities are not independent
of each other. M is the number of methods in the component, F the number of fields.
Msand Fsare thenumber of methods and fields exported inthe interface of thereusing
class, Mnsand Fnsthe number of non-exported methods and fields. The required work
of some features is explained further on in this paper, and is denoted with ‘...’ for now.
Note that Fs+ Fns= F,Ms+ Mns= M, and usually Fs? F ≪ Ms< M.
5. State Reuse: Declaring fields: O(F) → O(1) Reusing the state of a component
prevents a lot of duplication. For example, the state of an association is almost
always a simple reference. It makes no sense to force a developer to separately
provide that state every time he uses an association component.
6. Interface Reuse: Constructing interface: O(Ms+Fs) → O(1) Reusing the com-
ponent interface prevents duplication of its signatures. Aside from the exact method
names and types, which can be configured using renaming and type parameters, the
signatures in the reusing class are the same as those in the component interface.
1Many of the requirements are presented in related work under slightly different names.
Page 5
7. Selective Interface Reuse: Resolving conflicts: O(M + F) → O(Ms+ Fs)
A developer will usually expose only a part of the component interface based on
the intended use of the reusing class. Exposing its entire interface makes the reusing
class harder to understand if the component has a lot of functionality. In addition, it
can cause a large amount of name conflicts that must be solved even if the involved
methods and fields are not relevant in the context of the reusing class.
8. Powerful Selection: Selecting exported methods/fields: O(Ms+ Fs) → O(...)
Being able to select which methods and fields are exported in the interface of the
reusing class is not enough. If hiding or selecting is done individually for each
method, it requires too much work.
9. Default Separation: Separating components: O(Mns+Fns) → O(1) By default,
components – and thus their methods and instance variables – must be separated,
since that is how they are typically used. For example, the methods and fields of the
association components of Person must be kept separate. Separating all methods
manually is error-prone and requires separation of non-selected methods.
10. Mass Renaming: Renaming: O(Ms+ Fs) → O(...) Many components have
patterns in the names of their methods. For example, the methods for associations
are typically named getX, setX, isValidX, and so on. If such a pattern can be
exploited, all of its occurrences can be replaced with a single declaration.
11. High-level Dependencies: Resolving method dependencies: O(DM) → O(...)
Some components depend on methods of other components. For example, a com-
ponent for bidirectional associations needs the method of the other end of the asso-
ciation to maintain consistency, but it does not know their final names. Resolving
these dependencies individually is tedious and error-prone. In addition, if additional
dependencies are added between two components, all classes that reuse them must
add additional wiring code. By directly connecting entire components to each other,
all dependencies between them are resolved at once, and additional dependencies
require no additional wiring code. In the formula, DMis the number of method
dependencies of the reused component.
Completeness Features The following features increase the amount of functionality
of a component that can be reused.
12. Reuse of Hidden Functionality: Methods that are not exposed in the interface of
the reusing class – to prevent conflicts and interface bloat – may still be valuable to
clients. They should still be reusable, unless the developer explicitly forbids clients
to access them. Examples are advanced iteration methods for associations.
13. Reuse of Component Type: If an object cannot somehow be used as if it were
of the type of one of its components, certain methods cannot be reused. For ex-
ample, class BoundedValue has a method to transfer the remaining value to
another BoundedValue. If that method cannot be used to transfer the remain-
ing money from one bank account to another, it must be duplicated even though
BankAccount offers all required methods and fields. But if the bounded value
component of BankAccount can be used as a real BoundedValue, the transfer
method can be reused.