• No results found

Object-oriented systems

4.4 Data abstraction

4.4.1 Classes

The ultrasonic example involves several different types of object. Shear pulses are objects of the same type as each other, but of a different type from the detector. Data abstraction allows us to define these types and the functions that go with them. These object types are called classes, and they form templates for the objects themselves. Objects which represent the same idea or concept are grouped together as a class. The words type and class are generally equivalent and are used interchangeably here.

Most computer languages include some simple built-in data types, such as integer or real. An object-oriented language allows us to define additional data types (i.e., classes) that are treated identically, or nearly identically, to the built-in types. The user-defined classes are sometimes called abstract data types. These may be classes which have a specific role within a particular domain, such as Shear_pulse, Circle, or Polymer, or they may describe a specialized version of a standard data type, such as Big_integer.

The definition of a class contains the class name, its attributes (Section 4.4.3), its operations (Section 4.4.4) and its relationships to other classes (Sections 4.5 and 4.7). C++ and some other OOP languages require that the attribute types and the visibility from other classes also be specified.

4.4.2 Instances

It was stated in Section 4.4.1 that classes form templates for “the objects themselves,” meaning object instances. Once a class has been defined, instances of the class can be created that have the properties defined for the class. For example, pulse#259 might be an ultrasonic pulse whose location at a given moment is (x = 112mm, y = 294mm, z = 3.5mm). We would represent

pulse#259 as an instance of the class Longitudinal_pulse. As a more tangible example, my car is an instance of the class of car. The class specifies the characteristics and behavior of its instances. A class can, therefore, be thought of as the blueprint from which instances are built. The terms object

and instance are often used interchangeably, although it is helpful to use the latter to stress the distinction from a class.

This is just an extension of the concepts of data types and variables that exist in other programming languages. For instance, most languages include a built-in definition of the type integer. However, we can only draw upon the properties of an integer by creating an instance, i.e., an integer variable. In C, this would look like this:

int x; /* create an instance, x, of type int */

x = 3; /* manipulate x */

Similarly, once we have defined a class in an object-oriented language, its properties can only be used by creating one or more instances. Consider the class Longitudinal_pulse. One instance of this class is generated by the transmitter object (which is itself an instance of the class Transmitter). This instance represents one specific pulse, which has position, amplitude, phase, speed, and direction. When this pulse interacts with a defect (another instance), a new instance of Longitudinal_pulse must be created, since there will be both a transmitted and a reflected pulse (see Figure 4.3). The new pulse will have the same attributes as the old one because they are both derived from the same class, but some of these attributes will have different values associated with them (e.g., the amplitude and direction will be different).

4.4.3 Attributes (or data members)

A class definition includes its attributes and operations. The attributes are particular quantities that describe instances of that class. They are sometimes described as slots into which values are inserted. Thus the class Shear_pulse

might have attributes such as amplitude, position, speed, and direction. Only the names and, depending on the language, the types of the attributes need to be declared within the class, although values can optionally be supplied too. The class acts as a template, so that when a new shear pulse is created, it will contain the names of these attributes. The attribute values can be supplied or calculated for each instance, or a value provided within the class can be used as a default. The attributes can be of any type, including abstract data types, i.e., classes. Some languages, such as C++, require that the class definition defines the attribute types in advance. Amplitude and speed might be of type float, whereas position and direction would be of type vector.

If a value for an attribute is supplied in the class definition, any new instances would carry those values by default unless specifically overwritten. Most OOP languages distinguish between class attributes (or class variables) and instance attributes (or instance variables). The value of a class attribute

remains the same for all instances of that class. In contrast, instances contain their own copies of each instance attribute. If a default has been specified, each instance of given class has the same initial value for an instance attribute. However, the values of the instance attribute may subsequently vary from one instance to another. Thus, in the above example, speed might be a class attribute for Shear_pulse, since the speed will be the same for all shear pulses in a given material. On the other hand, amplitude and position are properties of each individual pulse and are represented as instance attributes.

In C++, attributes are called data members. All data members are assumed to be instance attributes unless they are declared static. Static data members are stored at the same memory location for every instance of a class, so there is only one copy, regardless of how many instances there might be. Static data members are, therefore, equivalent to class attributes.

4.4.4 Operations (or methods or member functions)

Each object has a set of operations or functions that it can perform. For example, a Shear_pulse object may contain the function move. This function might take as its parameters the amount and direction of movement, and return a new value for its position attribute. In some OOP languages, operations belonging to objects are called methods. In C++, they are called member functions. Operations are defined for a class, and can then be used by all instances of that class. It may also be possible for instances of other classes to access these operations (see Sections 4.5 and 4.6).

4.4.5 Creation and deletion of instances

Creating a new instance requires the computer to set aside some of its memory for storing the instance. Given a class definition, Myclass, the creation of new instances is similar in Smalltalk and in C++. In Smalltalk we might write:

Myinstance := Myclass new. "Myinstance is a global variable"

The words in quotes are a comment and are ignored by the compiler. The equivalent in C++ would be:

Myclass* myinstance;

// declare a pointer to objects of type Myclass myinstance = new Myclass;

// pointer now points to a new instance

Here the // symbol indicates a comment. The C++ version can be abbreviated to:

Myclass* myinstance = new Myclass;

// new pointer points to a new instance.

Large numbers of instances may be created while running a typical object- oriented system. An important consideration, therefore, is the release of memory that is no longer required. In the example of the ultrasonic simulation, new pulse instances are generated through reflections, but they must be removed when they reach the detector. The memory that is occupied by these unwanted instances must be released again if we are to avoid building a system with an insatiable appetite for computer memory. In the Smalltalk example above, Myinstance is a global variable, indicated by the capitalization of the first letter of the name. Global variables are accessible from any part of the program and are retained in the programming environment until explicitly deleted. It is more common to attach instances to temporary variables, shown here in Smalltalk:

|myinstance| "myinstance is declared as a temporary variable" myinstance := Myclass new.

Temporary variables exist only within the method in which they are declared, and their lifetime is that of the method activation. When execution of the method has finished, these variables (and deleted global variables) leave behind an area of “unowned” memory. The Smalltalk system automatically reclaims this memory — a process known as garbage collection. Depending on the particular implementation, garbage collection may cause the program to momentarily “freeze” while the system carries out its memory management. Garbage collection is a feature of Smalltalk, CLOS, and other OOP languages. Some implementations allow the programmer to influence the timing of garbage collection, while in other implementations garbage collection is an unnoticed background process.

In C++, the responsibility for memory management rests with the programmer. Objects which are created as described above must be explicitly destroyed when they are no longer needed:

delete myinstance;

This operation releases the corresponding memory. C++ also allows an alternative method of object creation and deletion which relies on the scope of objects. A new instance, myinstance, may be created within a block of code in the following way:

The instance exists only within that particular block of code, which is its scope. When the flow of execution enters the block of code, the object is automatically created. Similarly, it is deleted as soon as the flow of execution leaves the block.

C++ allows the programmer to define special functions to be performed when a new instance is created or deleted. These are known respectively as the constructor and the destructor. The constructor is a member function whose name is identical to the name of the class, and is typically used to set the initial values of some attributes. The destructor is defined in a similar way to the constructor, and its name is that of the class preceded by a tilde (~). It is mostly used to release memory when an instance is deleted. As an example, consider the definition for Sonic_pulse in C++:

// class definition: class Sonic_pulse { protected: float amplitude; public:

Sonic_pulse(float initial_amplitude); // constructor

~Sonic_pulse(); // destructor

};

// constructor definition:

Sonic_pulse::Sonic_pulse(float initial_amplitude) {

amplitude=initial_amplitude; // set up initial value

}

// destructor definition: Sonic_pulse::~Sonic_pulse() {

// perform any tidying up that may be necessary before // deleting the object

}

The class has a single attribute, amplitude, and this is set to an initial value by the constructor. The constructor is automatically called immediately after a new instance of Sonic_pulse is created. To create a new sonic pulse whose initial amplitude is 131.4 units, we would write in C++ either:

Sonic_pulse* myinstance = new Sonic_pulse(131.4); //technique 1

or:

If a destructor has been defined for a class, it is automatically called whenever an instance is deleted. If the instance was created by technique 1, it must be explicitly deleted by the programmer. If it was created by technique 2, it is automatically deleted when it becomes out of scope from the program’s thread of control.

Note that data members in C++ are normally given the prefix m_, so that

amplitude, above, would become m_amplitude. However, this convention has not been adopted here as it would confuse the comparisons between segments of C++ code and Smalltalk code.