Eiffel The Essentials
Eiffel The Essentials
This appendix addresses people who are familiar with the object-oriented approach but do not know Eiffel very well. It introduces all the concepts needed to understand the core of this thesis. However, it is not an exhaustive presentation of the Eiffel language. The reference manual of the current version of Eiffel is the book by Meyer Eiffel: The Language. The next version of the language is defined in the third edition of this book, which is currently available as a draft.
374
Classes
A class is a representation of an Abstract Data Type (ADT). Every object is an instance of a class. The object creation uses a so-called creation procedure, which is similar to the notion of constructor in languages such as Java or C#. A class is characterized by a set of features (operations), which may be either attributes or routines. (In Java/C# terminology, features tend to be called members, attributes are called fields and routines are called methods.) Eiffel further distinguishes between routines that return a result (functions) and routines that do not return a result (procedures). This is a classification by implementation: routines vs. attributes, namely computation vs. memory. There is another classification: by role. Features can be either commands (if they do not return a result) or queries (if they do return a result). Then, queries can be either functions if they involve some computation or attributes if the value is stored in memory. The following picture shows the different feature categories:
Command
No result
Procedure
No result
Routine
Returns result Computation
Feature
Returns result Computation
Function
Feature
Query
Memory
Memory
Attribute
Design principles
Eiffel is not only a programming language but also an object-oriented method to build high-quality software. As a method, it brings some design principles: As mentioned above, Eiffel distinguishes between commands and queries. Even if not enforced by any compiler, the Eiffel method strongly encourages following the Command/Query Separation principle: A feature should not both change the objects state and return a result about this object. In other words, a function should be side-effect-free. As Meyer likes to present it: Asking a question should not change the answer. Another important principle, which is Information Hiding: The supplier of a module (typically a class) must select the subset of the modules properties that will be available officially to its client (the public part); the remaining properties build the secret part. The Eiffel language provides the ability to enforce this principle by allowing to define fine-grained levels of availability of a class to its clients. Another principle enforced by the Eiffel method and language is the principle of Uniform Access, which says that all features offered by a class should be available through a uniform notation, which does not betray whether features are implemented through storage (attributes) or through computation (routines). Indeed, in Eiffel, one cannot know when writing x f whether f is a routine or an attribute; the syntax is the same.
375
Types
As mentioned earlier, in Eiffel, every object is an instance of a class. There is no exception; even basic types like INTEGERs or REALs are represented as a class. Besides, Eiffel is strongly typed; every program entity is declared of a certain type. A type is based on a class. In case of non-generic classes, type and class are the same. In the case of generic classes, a class is the basis for many different types. The majority of types are reference types, which means that values of a certain type are references to an object, not the object itself. There is a second category of types, called expanded types, where values are actual objects. It is the case of basic types in particular. For example, the value 5 of type INTEGER is really an object of type INTEGER with value 5, not a pointer to an object of type INTEGER with a field containing the value 5.
Structure of a class
The basic structure of an Eiffel class is the following:
class CLASS_NAME feature -- Comment ... feature -- Comment ... end
It starts with the keyword class and finishes with the keyword end, and in-between a set of features grouped by feature clauses introduced by the keyword feature and a comment. The comment is introduced by two consecutive dashes and is not compulsory (although recommended to improved readability and understandability).
Book example
An Eiffel class may contain other clauses than the basic ones just shown. For example, it may start with an indexing clause (introduced by the keyword indexing), which should gives general information about the class. The following class BOOK is a typical example of what an Eiffel class looks like. (If you do not understand everything, dont panic; each new notion will be described after the class text.)
indexing description: "Representation of a book" class BOOK create make
376
feature {NONE} -- Initialization
make (a_title: like title; some_authors: like authors) is -- Set title to a_title and authors to some_authors. require a_title_not_void: a_title /= Void a_title_not_empty: not a_title is_empty do title := a_title authors := some_authors ensure title_set: title = a_title authors_set: authors = some_authors end
feature -- Access title: STRING -- Title of the book authors: STRING -- Authors of the book -- (if several authors, of the form: -- "first_author, second_author, ...") feature -- Status report is_borrowed: BOOLEAN -- Is book currently borrowed (i.e. not in the library)? feature -- Basic operation borrow is -- Borrow book. require not_borrowed: not is_borrowed do is_borrowed := True ensure is_borrowed: is_borrowed end return is -- Return book. require is_borrowed: is_borrowed do is_borrowed := False ensure not_borrowed: not is_borrowed end invariant title_not_void: title /= Void title_not_empty: not title is_empty end
377
It may also have a require clause after the comment to introduce preconditions and an ensure clause before the end keyword to express postconditions. It is the case of the procedure make. This section will not say more about preconditions and postconditions for the moment. They will be covered in detail in the next section about Design by Contract. A routine may also have a local clause, listing the local variables used in this routine; it is located before the do keyword (after the require clause if any).
378
The typical use of assignment attempts is in conjunction with persistence mechanisms because one cannot be sure of the exact type of the persistent data being retrieved. The next two feature clauses Access and Status report introduce three attributes: title and authors of type STRING, and is_borrowed of type BOOLEAN. The general scheme for an attribute is the following:
attribute_name: ATTRIBUTE_TYPE -- Comment
In the current version of Eiffel, attributes cannot have preconditions or postconditions. It will be possible in the next version of the language. The routines borrow and return follow the same scheme as the feature make described before. It is worth mentioning though the two possible values for BOOLEANs, namely True and False, which are both keywords. The last part of the class is the invariant, which has two clauses in this particular example. Contracts (preconditions, postconditions, class invariants) are explained in the next section about Design by Contract. One last comment about the class BOOK: the use of Void. Void is a feature of type NONE defined in class ANY. It is the equivalent of null in other languages like C# or Java. It corresponds to a reference attached to no object.
Design by Contract
Design by Contract is a method of software construction, which suggests building software systems that will cooperate on the basis of precisely defined contracts.
379
Each precondition clause is of the form tag: expression where the tag can be any identifier and the expression is a boolean expression (the actual assertion). The tag is optional; but it is very useful for documentation and debugging purposes. Postconditions are properties that are satisfied at the end of the routine execution. They are benefits for the clients and obligations for the supplier. A postcondition violation is the manifestation of a bug in the supplier (which fails to satisfy what it guarantees to its clients).
380
Of course, a routine may have both preconditions and postconditions; hence both a require and an ensure clause (like in the previous BOOK class). Class invariants capture global properties of the class. They are consistency constraints applicable to all instances of a class. They must be satisfied after the creation of a new instance and preserved by all the routines of the class. More precisely, it must be satisfied after the execution of any feature by any client. (This rule applies to qualified calls of the form x f only, namely client calls. Implementation calls unqualified calls and calls to non-exported features do not have to preserve the class invariant.) Class invariants are introduced by the keyword invariant in an Eiffel class
class CLASS_NAME feature -- Comment ... invariant tag_1: boolean_expression_1 tag_2: boolean_expression_2 end
There are three other kinds of assertions: Check instructions: Expressions ensuring that a certain property is satisfied at a specific point of a methods execution. They help document a piece of software and make it more readable for future implementers. In Eiffel, check instructions are introduced by the keyword check as follows:
routine_name (arg_1: TYPE_1; arg_2: TYPE_2): RETURN_TYPE is -- Comment do ... Implementation here (set of instructions) check tag_1: boolean_expression_1 tag_2: boolean_expression_2 end ... Implementation here (set of instructions) end
Loop invariants: Conditions, which have to be satisfied at each loop iteration and when exiting the loop. They help guarantee that a loop is correct. Loop variants: Integer expressions ensuring that a loop is finite. It decreases by one at each loop iteration and has to remain positive. This appendix will show the syntax of loop variants and invariants later when introducing the syntax of loops.
381
382
feature {NONE} -- Initialization default_create is -- Create books. do create books make end
feature -- Access books: LINKED_LIST [BOOK] -- Books available in the library feature -- Element change extend (a_book: BOOK) is -- Extend books with a_book. require a_book_not_void: a_book /= Void a_book_not_in_library: not books has (a_book) do books extend (a_book) ensure one_more: books count = old book count + 1 book_added: books last = a_book end
..
remove (a_book: BOOK) is -- Remove a_book from books. require a_book_not_void: a_book /= Void book_in_library: books has (a_book) do books start books search (a_book) books remove ensure one_less: books count = old books count 1 book_not_in_library: not books has (a_book) end
. . .
..
feature -- Output display_books is -- Display title of all books available in the library. do if books is_empty then io put_string ("No book available at the moment") else from books start until books after loop io put_string (books item title) books forth end end end
..
. ..
.. .
383
.. . .
This example introduces two controls we had not encountered before: conditional structures (in feature display_books) and loops (in display_books and borrow_all). Here is the syntax scheme for conditional structures:
if some_condition_1 then do_something_1 elseif some_condition_2 then do_something_2 else do_something_else end
The elseif and else branches are optional. Here is the syntax scheme for loops (there is only one kind of loops in Eiffel):
from initialization_instructions invariant loop_invariant variant loop_variant until exit_condition loop loop_instructions end
Syntax of loops
The variant and invariant clauses are optional. The from clause is compulsory but it may be empty. Lets now discover the other Eiffel techniques used in this example:
Inheritance
The class LIBRARY contains a clause we have not seen yet: an inherit clause. It introduces the classes from which class LIBRARY inherits. Here LIBRARY inherits from ANY.
384
ANY is the class from which any Eiffel class inherits. If you remember, we saw before that NONE is the class that inherits from any Eiffel class, which means we have a closed hierarchy here:
ANY
NONE
In fact, one does not need to write that a class inherits from ANY; it is the default. Here, the class LIBRARY expresses the inheritance link explicitly to be able to redefine the feature default_create inherited from ANY. Redefinition allows changing the body of a routine (the do clause) and changing the routine signature if the new signature conforms to the parent one (which basically means that the base class of the new argument types inherits from the base class of the argument types in the parent, and same thing for the result type if it is a function). The routine body can be changed, but it still has to follow the routines contract. The Design by Contract method specifies precise rules regarding contracts and inheritance: preconditions are or-ed and can only be weakened, postconditions are and-ed and can only be strengthened (not to give clients any bad surprise). Class invariants are also and-ed in descendant classes (subclasses). As suggested by the inheritance figure on the previous page, a class may inherit from one or several other classes, in which case we talk about multiple inheritance. Contrary to languages like C# or Java, Eiffel supports multiple inheritance of classes. (It is restricted to interfaces in the Java/C# worlds.) Allowing multiple inheritance means that a class may get features from two different parents (superclasses) with the same name. Eiffel provides a renaming mechanisms to handle name clashes. For example, if we have a class C that inherits from A and B, and A and B both define a feature f, it is possible to rename the feature f from A as g to solve the name clash. Here is the Eiffel syntax:
class C inherit A rename f as g end B feature -- Comment ... end
Redefinition of return type and argument types follows the rules of covariance. See Non-conforming inheritance, page 391. See Design by Contract, page 378.
Renaming mechanism
385
Eiffel also enables to undefine a routine, that is to say making it deferred (abstract). A deferred feature is a feature that is not implemented yet. It has no do clause but a deferred clause. Yet it can have routine preconditions and postconditions. Here is the general structure of a deferred routine:
routine_name (arg_1: TYPE_1; arg_2: TYPE_2): RETURN_TYPE is -- Comment require ... Some precondition clauses deferred ensure ... Some postcondition clauses end
A class that has at least one deferred feature must be declared as deferred. In the current version of Eiffel, it is also true that a deferred class must have at least one deferred feature. In the next version of the language, it will be possible to declare a class deferred even if all its features are effective (implemented). Besides, contrary to other languages like Java or C#, a deferred class can declare attributes. It can also express a class invariant. To come back to inheritance and undefinition, here is the syntax that allows to make a routine deferred when inheriting it from a parent class:
deferred class C inherit A undefine f end feature -- Comment ... end
Undefinition mechanism
386
This example supposes that class A declares a feature f, which is effective in A. The class C inherits f from A and makes it deferred by listing it in the clause undefine. Therefore, class C must be declared as deferred (it has at least one deferred feature). Another problem that arises with multiple inheritance is the case of repeated inheritance (also known as the diamond structure). For example, we have a class D that inherits from B and C, which themselves inherit from A. Class A has a feature f. B renames it as g and redefines it. C leaves f unchanged. Here is the corresponding class diagram:
B f g g++ D
The class D inherits two different features from the same feature f in A. The problem occurs when talking about dynamic binding: which feature should be applied? There is another inheritance adaptation clause called select, which provides the ability to say: in case there is a conflict, use the version coming from this parent. For example, if we want to retain the feature g coming from B, we would write:
class D inherit B select g end C feature -- Comment ... end
Selection mechanism
The last inheritance adaptation clause is the export clause. It gives the possibility to restrict or enlarge the exportation status of an inherited routine. Here is the general scheme:
class B inherit A export {NONE} f -- Makes f secret in B (it may have been exported in A) {ANY} g -- Makes g exported in B (it may have been secret in A) {D, E} x, z -- Makes x and z exported to only classes D and E
Export mechanism
387
The last point we need to see about inheritance is the order of adaptation clauses. Here it is:
class D inherit B rename e as k export {NONE} all {D} r undefine m redefine b select g end C feature -- Comment ... end
Genericity
Lets come back to our class LIBRARY. The next mechanism we had not encountered before is genericity. Indeed, the class LIBRARY declares a list of books:
books: LINKED_LIST [BOOK] -- Books available in the library
See Class representation of a book library, page 381.
The attribute books is of type LINKED_LIST [BOOK], which is derived from the generic class LINKED_LIST [G], where G is the formal generic parameter. A generic class describes a type template. One must provide a type, called actual generic parameter (for example here BOOK), to derive a directly usable type like LINKED_LIST [BOOK] . Genericity is crucial for software reusability and extendibility. Besides, most componentized versions of design patterns rely on genericity. The example of LINKED_LIST [G] is a case of unconstrained genericity. There are cases where it is needed to impose a constraint on the actual generic parameters. Lets take an example. Say we want to represent vectors as a generic class VECTOR [G], which has a feature plus to be able to add two vectors, and we also want to be able to have vectors of vectors like VECTOR [VECTOR [INTEGER]].
388
Lets try to write the feature plus of class VECTOR [G]. In fact, it is unlikely to be called plus; it would rather be an infix feature +, which is a special notation to allow writing vector_a + vector_b instead of vector_a plus (vector_b). There also exists a prefix notation to be able to write - my_integer for example.
To come back to class VECTOR [G], a first sketch may look as follows:
class VECTOR [G] create make feature {NONE} -- Initialization make (max_index: INTEGER) is -- Initialize vector as an array with indexes from 1 to max_index. require ... do ... end feature -- Access count: INTEGER -- Number of elements in vector item (i: INTEGER): G is -- Vector element at index i require ... do ... end infix + (other: like Current): like Current is -- Sum with other require other_not_void: other /= Void consistent: other count = count local i: INTEGER do create Result make (1, count) from i := 1 until i > count loop Result put (item (i) + other item (i), i) -- Requires an operation + on elements of type G. i := i + 1 end ensure sum_not_void: Result /= Void end
Addable vectors
. .
389
It is not allowed to have multiple constraints, say class C [G > {A, B}]. It may be allowed in the next version of the language. Another kind of constraint is to impose that actual generic parameters must have certain creation procedures. For example, the notation:
class MY_CLASS [G > ANY create default_create end]
[Meyer 200?b].
means that any actual generic parameter of MY_CLASS must conform to ANY and expose default_create in its list of creation procedures (introduced by the keyword create in an Eiffel class text).
Agents
There is still one mysterious point in the class LIBRARY, the postcondition of borrow_ all:
ensure all_borrowed: books for_all (agent {BOOK} is_borrowed)
See Class representation of a book library, page 381.
What the postcondition of borrow_all does is to test for all items of type BOOK in the list books whether it is_borrowed. The postcondition will evaluate to True if all books are borrowed. But what does this agent mean? An agent is an encapsulation of a routine ready to be called. (To make things simple, you may consider an agent as a typed function pointer.) It is used to handle event-driven development. For example, if you want to associate an action with the event button is selected, you will write:
my_button select_actions extend (agent my_routine)
where my_routine is a routine of the class where this line appears. A typical agent expression is of the form:
agent my_function (?, a, b)
where a and b are closed arguments (they are set at the time of the agents definition) whereas ? is an open argument (it will be set at the time of any call to the agent). It is also possible to construct an agent with a routine that is not declared in the class itself. The syntax becomes:
agent some_object some_routine (?, a, b)
where some_object is the target of the call. It is a closed target. The agent expression used in the postcondition of borrow_all had an open target of type BOOK:
agent {BOOK} is_borrowed
390
To call an agent, you simply have to write:
my_agent call ([maybe_some_arguments])
where call is a feature of class ROUTINE. Here is the class diagram of the three agent types:
Calling an agent
See Business Object Notation (BON), page 394.
Agent types
PROCEDURE [BASE, ARGS -> TUPLE] FUNCTION [BASE, ARGS -> TUPLE, RES]
An important property of the Eiffel agent mechanism: it is completely type-safe. Agents are a recent addition to the Eiffel language. They were introduced in 1999. Therefore, they are not described in the reference manual Eiffel: The Language. However, there is an entire chapter of the next revision of the book devoted to agents.
Performance
Using agents has a performance overhead. To measure that overhead, I performed one million direct calls to a routine that does nothing and one million agent calls to the same routine. Without agents, the duration was two seconds (2s per call); with agents, it was fourteen seconds (14s per call), thus seven times as slow. But in practice, one calls routines that do something. Therefore I added an implementation to the routine (a loop that executes do_nothing, twenty times) and did the same tests. The results were the following: 33s (33s per call) without agents; 46s (46s per call) with agents; thus 1.4 times as slow. In a real application, the number of agent calls in the whole code will be less significant. Typically, no more than 5% of the feature calls will be calls to agents. Therefore the execution of an application using agents will be about 0.07 times as slow, which is a acceptable performance overhead in most cases.
ECMA standardization
The process started in March 2002 when ECMA accepted the proposal to standardize Eiffel. It resulted in the creation of a new working group TG4, part of the Technical Committee 39 (originally scripting languages, although this is just for historical reasons). The first group meeting took place at ETH Zurich in Switzerland in June 2002. Jan van den Beld, secretary general of ECMA, took part in the meeting and explained how the standardization work should proceed.
391
New mechanisms
This section presents some extensions to the Eiffel language that have been preapproved by the committee. (To be finally approved, a mechanism needs to be implemented in at least one Eiffel compiler. For the four extensions presented here, there is no doubt about the final acceptation. Besides, three of these extensions are already implemented at least in part.)
Assertions on attributes
As mentioned before, attributes cannot have contracts in the current version of Eiffel. Only routines can have contracts. This discrimination between routines and attributes goes against the Uniform Access principle presented at the beginning of this appendix. Indeed, clients should not have to know whether a feature is implemented by computation or by storage; they just need to know that the class offers this service, no matter what its implementation is. Therefore the team agreed to introduce a new syntax for attributes, with a new keyword attribute, that allows putting preconditions and postcondition:
attribute_name: ATTRIBUTE_TYPE is -- Comment require ... Some precondition clauses attribute ensure ... Some postcondition clauses end
See Structure of an Eiffel attribute, page 378. See Design principles, page 374.
Assertions on attributes
Non-conforming inheritance
In the current version of Eiffel, inheritance always brings conformance. For example, if a class B inherits from a class A, then type B conforms to type A. As a consequence, an assignment like a1 := b1 where b1 is of type B and a1 is declared of type A is allowed. It is also possible to pass an instance of type B as argument of a feature expecting an A (for example, f (b1) with f declared as f (arg: A)).
392
However, sometimes, it may be useful to have inheritance without conformance, namely having subclassing without polymorphism. Non-conforming gives descendant classes access to the parents features but forbids such assignments and arguments passing as those described above (between entities of the descendant type and entities of the parent type). This facility is useful only in specific cases like implementation inheritance. For example, we could have a class TEXTBOOK that inherits from BOOK. This inheritance relation should be conformant; we want both subclassing and polymorphism. But this class TEXTBOOK may need access to some helper features defined in a class TEXTBOOK_SUPPORT. One way to get access to these features is to declare an attribute of type TEXTBOOK_SUPPORT and have a client relationship. Another way to do it is to use what is usually called implementation inheritance, that is to say inheriting from TEXTBOOK_SUPPORT just to be able to use these features. In that case, we just need subclassing (to get the features), not conformance (we dont want to assign an entity of type TEXTBOOK to an entity of type TEXTBOOK_SUPPORT). Hence the use of non-conforming inheritance. Another example taken from EiffelBase is the class ARRAYED_LIST, which inherits from LIST and ARRAY. For the moment, both inheritance links are conformant (there is no other choice!). But what we really want is a class ARRAYED_ LIST that inherits from LIST in a conformant way and a non-conformant link between ARRAYED_LIST and ARRAY (just to get the features of ARRAY, which are useful for the implementation of ARRAYED_LIST). The syntax is not completely fixed yet. For the moment, the idea is to use the keyword expanded in front of the parent class name in the inherit clause to specify that the inheritance link is non-conformant (hence the name expanded inheritance, which is sometimes used):
class B inherit C expanded A feature -- Comment ... end
What is the relation between non-conforming inheritance and expanded types? If a class B inherits from a class A, which is declared as expanded, then there is no conformance between B and A. Hence the use of the keyword expanded.
[EiffelBase-Web].
393
If you have an instance of type TYPE_1 and you want to add it to an instance of MY_ CLASS, the instance of TYPE_1 will be automatically converted to an instance of MY_ CLASS by calling the conversion procedure from_type_1, which needs to be declared as a creation procedure. On the other hand, if you have an instance of type MY_CLASS and you want to add it to an instance of type TYPE_2 , your instance of MY_CLASS will be automatically converted to TYPE_2 by calling the function to_type_2. Here are typical cases that this new mechanism permits to write:
my_attribute: MY_TYPE attribute_1: TYPE_1 attribute_2: TYPE_2 my_attribute + attribute_1 -- Equivalent to: -- my_attribute + create {MY_TYPE} from_type_1 (attribute_1)
Thus, it becomes possible to add complex numbers and integers cleanly and completely transparently to the clients. Part of the automatic type conversion mechanism is already implemented in the ISE Eiffel compiler.
Frozen classes
Another mechanism pre-approved at ECMA is to allow frozen classes in Eiffel, meaning classes from which one cannot inherit. The syntax is simple: the header of a frozen class is extended with the keyword frozen as shown next:
frozen class MY_CLASS
Section 18.3 describes the syntax and semantics of frozen classes in detail.
394
This extension is directly useful to the work presented in this thesis: it enables writing correct singletons in Eiffel, which is not possible with the current version of the language. Frozen classes are not supported by classic Eiffel compilers yet but they are already accepted by the ISE Eiffel for .NET compiler.
The method
The Business Object Notation (BON) is a method for analysis and design of objectoriented systems, which emphasizes seamlessness, reversibility and Design by Contract. It was developed between 1989 and 1993 by Jean-Marc Nerson and Kim Waldn to provide the Eiffel programming language and method with a notation for analysis and design. BON stresses simplicity and well-defined semantics. In that respect, it is almost at the opposite of widely-used design notations such as the Unified Modeling Language (UML) or the Rationale Uniform Process (RUP). As mentioned above, one priority of BON is to bring seamlessness into software development, to narrow the gap between analysis, design, and implementation by using the same concepts and the same semantics for the notation on the tree stages. Therefore BON does not have state-charts or entity-relationship diagrams like in UML because they are not compatible with what is available at the implementation level and would prevent reversibility. Instead, BON relies on pure object-oriented concepts like classes, client and inheritance relationships.
Notation
Here is a catalog of the notations used throughout this thesis: In BON, classes are represented as ellipses, sometimes referred to as bubbles:
NAME_OF_ NON_GENERIC_CLASS
The ellipse may contain information about the class properties, for example: Deferred class: Class that is declared as deferred.
* CLASS_NAME
Effective class: Class that is not declared as deferred but has at least one deferred parent, or redefines at least one feature.
+ CLASS_NAME
395
CLASS_NAME
Interfaced class: Class that interfaces with other programming languages (for example C) through external features.
CLASS_NAME
Reused class: Class that comes from a precompiled library. (ISE Eiffel compiler provides the ability to compile a library once and for all and then reuse it in so-called precompiled form.)
CLASS_NAME
Root class: Class that is the programs entry point.
CLASS_NAME
It is also possible to specify the features of a class by writing the feature names next to the ellipse representing the class:
* CLASS_NAME feature_name*
Effective feature: Feature that was deferred in the parent and is implemented in the current class.
+ CLASS_NAME feature_name+
Undefined feature: Feature that was effective in the parent and is made deferred in the current class through the undefinition mechanism.
CLASS_NAME feature_name-
396
CLASS_NAME feature_name++
BON also provides a notation for feature renaming:
CLASS_NAME f g
Classes may be grouped into clusters, which are represented as red stippled-rounded rectangles:
cluster name
CLASS_NAME
CLIENT_CLASS_NAME
feature_name
SUPPLIER_CLASS_NAME
PARENT_CLASS_NAME
PARENT_CLASS_NAME
HEIR_CLASS_NAME