A class is the implementation of an abstract data type (ADT). It defines attributes and methods which implement the data structure and operations of the ADT, respectively. Instances of classes are called objects. Consequently, classes define properties and behaviour of sets of objects.
An object is an instance of a class. It can be uniquely identified by its name and it defines a state which is represented by the values of its attributes at a particular time.
A program consisting of four objects.
An object is an entity that has state, behavior, and identity. The structure and behavior of similar objects are defined in their common class. The terms instance and object are interchangeable. the developer with an object-oriented mindset begins to think that everything in the world is an object. This perspective is a little naive because some things are distinctly not objects. For example, attributes such as beauty or color are not objects, nor are emotions such as love and anger. On the other hand, these things are all potentially properties of other objects. For example, we might say that a man (an object) loves his wife (another object), or that a particular cat (yet another object) is gray.
State :
Example
Consider an abstraction of an employee record. Figure 3–1 depicts this abstraction using the Unified Modeling Language notation for a class.
Consider an abstraction of an employee record. Figure 3–1 depicts this abstraction using the Unified Modeling Language notation for a class.
Each part of this abstraction denotes a particular property of our abstraction of an employee. This abstraction is not an object because it does not represent a specific instance. When made specific, we may have, for example, two distinct objects: Tom and Kaitlyn, each of which takes up some amount of space in memory (see Figure 3–2).
It is good engineering practice to encapsulate the state of an object rather than expose it. For example, we might change the abstraction (class) as shown in Figure 3–3.
Behavior :
Behavior is how an object acts and reacts, in terms of its state changes and message passing
Behavior is how an object acts and reacts, in terms of its state changes and message passing
An operation is some action that one object performs on another in order to elicit a reaction. For example, a client might invoke the operations append and pop to grow and shrink a queue object, respectively. A client might also invoke the operation length, which returns a value denoting the size of the queue object but does not alter the state of the queue itself.
Operations –
five kinds of operations on an object.
- Modifier: an operation that alters the state of an object
- Selector: an operation that accesses the state of an object but does not alter the state
- Iterator: an operation that permits all parts of an object to be accessed in some well-defined order
- Constructor: an operation that creates an object and/or initializes its state
- Destructor: an operation that frees the state of an object and/or destroys the object itself
Roles and Responsibilities –
Objects play many different roles during their lifetimes . state and behavior of an object collectively define the roles that an object may play in the world
Identity :
“most programming and database languages use variable names to distinguish temporary objects, mixing addressability and identity.
Example
Let us assume we instantiate a number of DisplayItem classes as indicated in Figure 3–4a. Specifically, the manner in which we instantiate these classes sets aside four locations in memory whose names are item1,item2, item3, and item4, respectively. Here, item1 is the name of a distinct DisplayItem object, but the other three names each denote a pointer to a DisplayItem object. Only item2 and item3 actually point to distinct DisplayItem objects (because in their declarations we allocated a new DisplayItem object); item4 designates no such object. Furthermore, the names of the objects pointed to by item2 and item3 are anonymous: We can refer to these distinct objects only indirectly, via their pointer value.
Let us assume we instantiate a number of DisplayItem classes as indicated in Figure 3–4a. Specifically, the manner in which we instantiate these classes sets aside four locations in memory whose names are item1,item2, item3, and item4, respectively. Here, item1 is the name of a distinct DisplayItem object, but the other three names each denote a pointer to a DisplayItem object. Only item2 and item3 actually point to distinct DisplayItem objects (because in their declarations we allocated a new DisplayItem object); item4 designates no such object. Furthermore, the names of the objects pointed to by item2 and item3 are anonymous: We can refer to these distinct objects only indirectly, via their pointer value.
Although item1 and the object designated by item2 have the same state, they represent distinct objects. Also, note that we have changed the state of the object designated by item3 by operating on it through its new indirect name, item4. This is a situation we call structural sharing, meaning that a given object can be named in more than one way; in other words, there are aliases to the object. Structural sharing is the source of many problems in object-oriented programming. Failure to recognize the side effects of operating on an object through aliases often leads to memory leaks, memory access violations, and, even worse, unexpected state changes. For example, if we destroyed the object designated by item3, then item4’s pointer value would be meaningless; this is a situation we call a dangling reference.
Consider also Figure 3–4c, which illustrates the results of modifying the value of the item2 pointer to point to item1. Now item2 designates the same object as item1. Unfortunately, we have introduced a memory leak: The object originally designated by item2 can no longer be named, either directly or indirectly, and so its identity is lost. In languages such as Smalltalk and Java, such objects will be garbage-collected and their storage reclaimed automatically, but in languages such as C++, their storage will not be reclaimed until the program that created them finishes. Especially for long-running programs, memory leaks such as this are either bothersome or disastrous.
Copying, Assignment, and Equality :
<**** will add soon *****>
Relationships among Objects :
Objects contribute to the behavior of a system by collaborating with one another.
Two kinds of object relationships are of particular interest in object-oriented analysis and design, namely:
1.Links
2.Aggregation
Links -
The term link derives from Rumbaugh et al., who define it as a “physical or conceptual connection between objects” [16]. An object collaborates with other objects through its links to these objects. Stated another way, a link denotes the specific association through which one object (the client) applies the services of another object (the supplier), or through which one object may navigate to another.
As a participant in a link, an object may play one of three roles.
1.Controller: This object can operate on other objects but is not operated on by other objects. In some contexts, the terms active object and controller are interchangeable.
2.Server: This object doesn’t operate on other objects; it is only operated on by other objects.
3.Proxy: This object can both operate on other objects and be operated on by other objects. A proxy is usually created to represent a real-world object in the domain of the application
In the context of Figure 3–5, FlowController acts as a controller object, DisplayPanel acts as a server object, and Valve acts as a proxy. Example 3–3 illustrates how responsibilities can be properly separated across a group of collaborating objects.
Aggregation –
Whereas links denote peer-to-peer or client/supplier relationships, aggregation denotes a whole/part hierarchy, with the ability to navigate from the whole (also called the aggregate) to its parts. In this sense, aggregation is a specialized kind of association. For example, as shown in Figure 3–6, the object Temperature-Controller has a link to the object TemperatureRamp as well as to Heater. The object TemperatureController is thus the whole, and Heater is one of its parts
Aggregation may or may not denote physical containment. For example, an airplane is composed of wings, engines, landing gear, and so on: This is a case of physical containment. On the other hand, the relationship between a shareholder and his or her shares is an aggregation relationship that does not require physical containment. The shareholder uniquely owns shares, but the shares are by no means a physical part of the shareholder. Rather, this whole/part relationship is more conceptual and therefore less direct than the physical aggregation of the parts that form an airplane.
The Nature of a Class :
an object is a concrete entity that exists in time and space, a class repre- sents only an abstraction “A class represents a set of objects that share a common structure and a common behavior.”A single object is simply an instance of a class.
Interface and Implementation –
The interface of a class provides its outside view and therefore emphasizes the abstraction while hiding its structure and the secrets of its behavior. This interface primarily consists of the declarations of all the operations applicable to instances of this class, but it may also include the declaration of other classes, constants, variables, and exceptions as needed to complete the abstraction. By contrast, the implementation of a class is its inside view, which encompasses the secrets of its behavior. The implementation of a class primarily consists of the implementation of all of the operations defined in the interface of the class.
We can further divide the interface of a class into four parts:
1.Public: a declaration that is accessible to all clients
2.Protected: a declaration that is accessible only to the class itself and its sub- classes
3.Private: a declaration that is accessible only to the class itself
4.Package: a declaration that is accessible only by classes in the same package
Visibility and Friendship –
In particular, C++ allows a developer to make explicit distinctions among all four of these different parts.
The C++ friendship mechanism permits a class to distinguish certain privileged classes that are given the rights to see the class’s protected and private parts. Friendships break a class’s encapsulation and so, as in life, must be chosen carefully. Java does not have friendship. Instead, Java has a somewhat similar type of visibility called package access, where all classes in the same package can access each other. Aside from friendship, public, protected, and private access operate in Java as they do in C++
The state of an object must have some representation in its corresponding class and so is typically expressed as constant and variable declarations placed in the protected or private part of a class’s interface. In this manner, the representation common to all instances of a class is encapsulated, and changes to this representation do not functionally affect any outside clients.
Relationships among Classes :
Consider for a moment the similarities and differences among the following classes of objects: flowers, daisies, red roses, yellow roses, petals, and ladybugs.
We can make the following observations.
- A daisy is a kind of flower.
- A rose is a (different) kind of flower.
- Red roses and yellow roses are both kinds of roses.
- A petal is a part of both kinds of flowers.
- Ladybugs eat certain pests such as aphids, which may be infesting certain kinds of flowers.
In all, there are three basic kinds of class relationships [22]. The first of these is generalization/specialization, denoting an “is a” relationship. For instance, a rose is a kind of flower, meaning that a rose is a specialized subclass of the more general class, flower.
The second is whole/part, which denotes a “part of” relation-ship. Thus, a petal is not a kind of a flower; it is a part of a flower.
The third is association, which denotes some semantic dependency among otherwise unre-lated classes, such as between ladybugs and flowers. As another example, roses and candles are largely independent classes, but they both represent things that we might use to decorate a dinner table
Association –
Of these different kinds of class relationships, associations are the most general but also the most semantically weak. The identification of associations among classes is often an activity of analysis and early design,
Example
For a vehicle, two of our key abstractions include the vehicle and wheels. As shown in Figure 3–7, we may show a simple association between these two classes: the class Wheel and the class Vehicle. (Arguably, an aggregation would be better.) By implication, this association suggests bidirectional navigation. Given an instance of Wheel, we should be able to locate the object denoting its Vehicle, and given an instance of Vehicle, we should be able to locate all the wheels
For a vehicle, two of our key abstractions include the vehicle and wheels. As shown in Figure 3–7, we may show a simple association between these two classes: the class Wheel and the class Vehicle. (Arguably, an aggregation would be better.) By implication, this association suggests bidirectional navigation. Given an instance of Wheel, we should be able to locate the object denoting its Vehicle, and given an instance of Vehicle, we should be able to locate all the wheels
Here we show a one-to-many association: Each instance of Wheel relates to one Vehicle, and each instance of Vehicle may have many Wheels
Multiplicity –
Our example introduced a one-to-many association, meaning that for each instance of the class Vehicle, there are zero (a boat, which is a vehicle, has no wheels) or more instances of the class Wheel, and for each Wheel, there is exactly one Vehicle. This denotes the multiplicity of the association. In practice, there are three common kinds of multiplicity across an association:
1.One-to-one
2.One-to-many
3.Many-to-many
A one-to-one relationship denotes a very narrow association. For example, in retail telemarketing operations, we would find a one-to-one relationship between the class Sale and the class CreditCardTransaction: Each sale has exactly one corresponding credit card transaction, and each such transaction corresponds to one sale. Many-to-many relationships are also common. For example, each instance of the class Customer might initiate a transaction with several instances of the class SalesPerson, and each such salesperson might interact with many different customers
Inheritance -
generalization/specialization relationship (“is a” hierarchy among classes)
“A subclass may inherit the structure and behavior of its superclass.”
As for the class ElectricalData, this class inherits the structure and behavior of the class TelemetryData but adds to its structure (the additional voltage data), redefines its behavior (the function transmit) to transmit the additional data, and can even add to its behavior (the function currentPower, a function to provide the current power level).
Single Inheritance:
inheritance is a relationship among classes wherein one class shares the structure and/or behavior defined in one (single inheritance) or more (multiple inheritance) other classes class from which another class inherits its superclass. class that inherits from one or more classes a subclass
Figure 3–9 illustrates the single inheritance relationships deriving from the superclass TelemetryData. Each directed line denotes an “is a” relationship. For example, CameraData “is a” kind of SensorData, which in turn “is a” kind of TelemetryData
Classes with no instances are called abstract classes. An abstract class is written with the expectation that its subclasses will add to its structure and behavior, usually by completing the implementation of its (typically) incomplete methods.
Polymorphism –
polymorphism, by which symbols such as + could be defined to mean different things. We call this concept overloading. In C++, one may declare functions having the same names, as long as their invocations can be distinguished by their signatures, consisting of the number and types of their arguments
Early Binding (static) vs Late binding (Dynamic) :
Polymorphism and late binding go hand in hand. In the presence of polymorphism, the binding of a method to a name is not determined until execution. In C++, the developer may control whether a member function uses early or late binding. Specifically, if the method is declared as virtual, then late binding is employed, and the function is considered to be polymorphic. If this virtual declaration is omitted, then the method uses early binding and thus can be resolved at the time of compilation. Java simply performs late binding without the need for an explicit declaration such as virtual. How an implementation selects a particular method for execution is described in the sidebar, Invoking a Method.
Invoking a Method
Consider the class hierarchy in Figure 3–10, which shows the base class DisplayItem along with three subclasses named Circle, Triangle, and Rectangle. Rectangle also has one subclass, named SolidRectangle.
In the class DisplayItem, suppose that we define the instance variable theCenter (denoting the coordinates for the center of the displayed item), along with the following operations as in our earlier example:
■ draw Draw the item.
■ move Move the item.
■ location Return the location of the item.
The operation location is common to all subclasses and therefore need not be redefined, but we expect the operations draw and move to be redefined since only the subclasses know how to draw and move themselves
The class Circle must include the instance variable theRadius and appropriate operations to set and retrieve its value. For this subclass, the redefined operation draw draws a circle of the given radius, centered on theCenter. Similarly, the class Rectangle must include the instance variables theHeight and theWidth, along with appropriate operations to set and retrieve their values. For this subclass, the operation draw draws a rectangle with the given height and width, again centered on theCenter. The subclass SolidRectangle inherits all characteristics of the class Rectangle but again redefines the behavior of the operation draw. Specifically, the implementation of draw for the class SolidRectangle first calls draw as defined in its superclass Rectangle (to draw the outline of the rectangle) and then fills in the shape. The invocation of draw demands polymorphic behavior.
Suppose now that we have some client object that wishes to draw all of the subclasses. In this situation, the compiler cannot statically generate code to invoke the proper draw operation because the class of the object being operated on is not known until runtime.
More strongly typed languages such as C++ do let the developer assert such information. Because we want to avoid method dispatch wherever possible but must still allow for the occurrence of polymorphic dispatch, invoking a method in these languages proceeds a little differently than in Smalltalk.
In C++, the developer can decide whether a particular operation is to be bound late by declaring it to be virtual; all other methods are considered to be bound early, and thus the compiler can statically resolve the method call to a simple subprogram call. To handle virtual member functions, most C++ implementations use the concept of a vtable, which is defined for each object requiring polymorphic dispatch, when the object is created (and thus when the class of the object is fixed). This table typically consists of a list of pointers to virtual functions.
For example, if we create an object of the class Rectangle, then the vtable will have an entry for the virtual function draw, pointing to the closest implementation of draw. If, for example, the class DisplayItem included the virtual function Rotate, which was not redefined in the class Rectangle, then the vtable entry for Rotate would point to the implementation of Rotate in the class DisplayItem. In this manner, runtime searching is eliminated: Referring to a virtual member function of an object is just an indirect reference through the appropriate pointer, which immediately invokes the correct code without searching
Multiple Inheritance:
With single inheritance, each subclass has exactly one superclass. Consider for a moment how one might organize various assets such as savings accounts, real estate, stocks, and bonds. Savings accounts and checking accounts are both kinds of assets typically managed by a bank, so we might classify both of them as kinds of bank accounts, which in turn are kinds of assets. Stocks and bonds are managed quite differently than bank accounts, so we might classify stocks, bonds, mutual funds, and the like as kinds of securities, which in turn are also kinds of assets.
Unfortunately, single inheritance is not expressive enough to capture this lattice of relationships, so we must turn to multiple inheritance. Figure 3–11 illustrates such a class structure. Here we see that the class Security is a kind of Asset as well as a kind of InterestBearingItem. Similarly, the class BankAccount is a kind of Asset, as well as a kind of InsurableItem and InterestBearingItem.
Designing a suitable class structure involving inheritance, and especially involving multiple inheritance, is a difficult task. This is often an incremental and iterative process. Two problems present themselves when we have multiple inheritance: How do we deal with name collisions from different superclasses, and how do we handle repeated inheritance?
Name collisions are possible when two or more different superclasses use the same name for some element of their interfaces, such as instance variables and methods. For example, suppose that the classes InsurableItem and Asset both have attributes named presentValue, denoting the present value of the item. Since the class RealEstate inherits from both of these classes, what does it mean to inherit two operations with the same name? This in fact is the key difficulty with multiple inheritance: Clashes may introduce ambiguity in the behavior of the multiply inherited subclass.
There are three basic approaches to resolving this kind of clash. First, the language semantics might regard such a clash as illegal and reject the compilation of the class. Second, the language semantics might regard the same name introduced by different classes as referring to the same attribute. Third, the language semant ics might permit the clash but require that all references to the name fully qualify the source of its declaration.
The second problem is repeated inheritance, which Meyer describes as follows:
“One of the delicate problems raised by the presence of multiple inheritance is what happens when a class is an ancestor of another in more than one way. If you allow multiple inheritance into a language, then sooner or later someone is going to write a class D with two parents B and C, each of which has a class A as a parent—or some other situation in which D inherits twice (or more) from A. This situation is called repeated inheritance and must be dealt with properly” [41]. As an example, suppose that we define the (ill-conceived) MutualFund class as a subclass of the classes Stock and Bond. This class introduces repeated inheritance of the class Security, which is a superclass of both Stock and Bond (see Figure 3–11).
There are various approaches to dealing with the problem of repeated inheritance.
First, we can treat occurrences of repeated inheritance as illegal. Second, we can permit duplication of superclasses but require the use of fully qualified names to refer to members of a specific copy. Third, we can treat multiple references to the same class as denoting the same class. Different languages handle this approach differently.
Aggregation –
Whole/part relationship (has-a relationship)
As we show in Figure 3–12, the class TemperatureController denotes the whole, and the class Heater is one of its parts. In the case of the class TemperatureController, we have aggregation as containment by value, a kind of physical containment meaning that the Heater object does not exist independently of its enclosing Temperature Controller instance. Rather, the lifetimes of these two objects are intimately connected:
When we create an instance of Temperature Controller, we also create an instance of the class Heater. When we destroy our TemperatureController object, by implication we also destroy the corresponding Heater object.
Composition –
A less direct kind of aggregation is also possible, called composition, which is containment by reference. In this case, the class Temperature Controller still denotes the whole, and an instance of the class Heater is still one of its parts, although that part must now be accessed indirectly. Hence, the lifetimes of these two objects are not so tightly coupled as before: We may create and destroy instances of each class independently.
On Building Quality Classes and Objects :
In our experience, the design of classes and objects is an incremental, iterative process. Frankly, except for the most trivial abstractions, we have never been able to define a class exactly right the first time. It takes time to smooth the conceptual jagged edges of our initial abstractions
Measuring the Quality of an Abstraction –
How can one know if a given class or object is well designed? We suggest five meaningful metrics:
1.Coupling
2.Cohesion
3.Sufficiency
4.Completeness
5.Primitiveness
define coupling as “the measure of the strength of association established by a connection from one module to another. Strong coupling complicates a system since a module is harder to understand, change, or correct by itself if it is highly interrelated with other modules. Complexity can be reduced by designing systems with the weakest possible coupling between modules”
Coupling with regard to modules still applies to object-oriented analysis and design, but coupling with regard to classes and objects is equally important. How-ever, there is tension between the concepts of coupling and inheritance because inheritance introduces significant coupling. On the one hand, weakly coupled classes are desirable; on the other hand, inheritance—which tightly couples superclasses and their subclasses—helps us to exploit the commonality among abstractions.