An object has:
Q1.2: What is the meaning of state of an object? (Ref: 3.1, p.84)
The state of an object encompasses the following two aspects:
For example, consider the following class:
class PersonnelRecord{ public: char* employeeName() const; int employeeSocialSecurityNumber() const; char* employeeDepartment() const; protected: char name[100]; int socialSecurityNumber; char department[10]; float salary; };In the above example, the state of a "PersonnelRecord" object is described by the protected data members. All the presentation of the state are hidden from all other outside client.
The relation of the object state and the object behavior, see the next question.
Q1.3: What is the meaning of behavior of an object? (Ref: 3.1, p.86)
No object exists in isolation. Rather, objects are acted upon, and themselves act upon other objects.
Behavior is how an object acts and reacts, in terms of its state changes and message passing.
The behavior of an object is function of its state as well as the operation performed upon it, with certain operations having side effect of altering the object's state.
Thus the state of an objects represents the cumulative results of its behavior.
Q1.4: What is operation of an object? (Ref: 3.1, p.88)
An operation denotes a service that a class offers to its clients. In practice, there are five kinds of operations a client performs upon an object:
In pure oo programming languages such as Smalltalk, operations may only be declared as methods, since the language does not allow us to declare procedures or functions separate from any class.
In contrast, C++ (and Object Pascal, Ada, etc) allows the developer to write operations as free subprograms. In C++, these are called non-member functions. Free subprograms are procedures or functions that serve as nonprimitive operations upon an object or objects of the same or different classes. Free subprograms are typically grouped according to the classes upon which they are built.
Booch calls such collections of free subprograms class utilities.
It is common style in C++ (and Smalltalk) to collect all logically related free subprograms and declare them as part of a class that has no state.
Thus, we may say that all methods are operations, but not all operations are methods. Some operations may be expressed as free subprograms. In practice, we are inclined to declare most operations as methods, although there are sometimes compelling reasons to do otherwise such as:
A particular operation affects two or more objects of different classes, and there is no particular benefit in declaring that operation in one class over the other.
Q1.6: What are roles and responsibilities? (Ref: 3.1, p.90)
Unifying the definitions of state and behavior, the term responsibilities are used. The responsibilities of an object are all the services it provides for all of the contracts it supports.
The state and behavior of an object collectively define the roles that an object may play in the world, which in turn fulfill the abstraction's responsibilities.
Sometimes objects play many different roles during their lifetime. For example:
We often start of our analysis of a problem by examining the various roles that an object plays. During design, we refine these roles by inventing the particular operations that carry out each role's responsibilities.
Q1.7: What is the relation between object and state machine? (Ref: 3.1, p.90)
The existence of state within an object means that the order in which operations are invoked is important. This give rise to the idea that each object is like a tiny, independent machine. Indeed, for some objects, this event- and time-ordering of operations is so pervasive that we can best formally characterize the behavior of such objects in terms of an equivalent finite state machine.
Continuing the machine metaphor, we may classify objects as either active or passive. An active object is one that encompasses its own thread of control, whereas a passive object does not.
If our system involves multiple threads of control, then we will usually have multiple active objects. Sequential systems, on the other hand , usually have exactly one active object, such as a main window object responsible for managing an event loop that dispatches message. In such architectures, all other objects are passive, and their behavior is ultimately triggered by messages from the one active object.
Q1.8: What is the meaning of identity of an object? (Ref: 3.1, p.91)
Identity is that property of an object which distinguishes it from all other object.
The failure to recognize the difference between the name of an object and the object itself is the source of many kinds of errors in object-oriented programming.
Consider a class that denotes a display item. A display item is a common abstraction in all GUI-centric system. First, we start with a simple structure that denotes a point in space:
struct Point{ int x; int y; Point() : x(0), y(0) {}; Point(int xValue, int yValue) : x(xValue), y(yValue) {}; };The reason Point is declared as structure is that it represents a simple record of other objects and has no really interesting behavior that applies to the object as a whole.
Then we declare the class which abstracts display items:
class DisplayItem{ public: DisplayItem(); DisplayItem(const Point& location); virtual ~DisplayItem(); virtual void draw() virtual void erase(); virtual void select(); virtual void unselect(); virtual void move(const Point& location); int isSelected() const; Point location() const; int isUnder(const Point& location) const; protected: ... };
Then we create instances of this class:
DisplayItem item1; DisplayItem* item2 = new DisplayItem(Point(75, 75)); DisplayItem* item3 = new DisplayItem(Point(100, 100)); DisplayItem* item4 = 0;
By the above declarations, four names and three distinct objects are created:
item1
is the name of distinct DisplayItem object,
item2
and item3
point to distinct DisplayItem
object,
item4
occupies the location in memory, it designates
no distinct DisplayItem.
Q1.9: What is the structural sharing? (Ref: 3.1, p.94)
The structural sharing means that a give 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 upon an object through aliases often leads to memory leaks, memory-access violation, and even worse, unexpected state changes.
Consider the following operations in the example of Q1.8,
item2 = &item1; item4->move(item2->location());In the above example introduces a memory leak. The object originally designated by
item2
can no longer be named, either
directly or indirectly, and so it identity is lost. In languages such as
Smalltalk and CLOS, 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.
Q1.10: What is object copying? (Ref: 3.1, p.94)
Consider the following declarations:
void highlight(DisplayItem& i); void drag(DisplayItem i);In the first declaration, the argument is passed by reference. This semantics only involve copying reference, not state, of the object. In general, passing objects by reference is the most desirable practice for nonprimitive objects, because it is far more efficient for passing anything larger than simple values.
In the second declaration, the argument is passed by values, which invokes a copy of the actual object specified by the argument. Therefore, the object passed by the argument is completely different one as denoted by the argument. In C++, it is possible to control the semantics of copying by a copy constructor to a class's declaration. Omitting this copy constructor invokes the default copy copy constructor, whose semantics are defined as a memberwise copy.
For objects whose state itself involves pointers or references to other objects, default memberwise copying is usually dangerous, for copying them implicitly introduces lower-level aliases.
The rule of thumb we apply, therefore is that we omit an explicit copy constructor only for those abstractions whose state consists of simple, primitive values. In all other cases, we usually provide an explicit copy constructor.
This practice distinguishes what some languages call shallow versus deep copying. Usually these two terms means:
Q1.11: What is object assignment? (Ref: 3.1, p.95)
Assignment is generally a copying operation, and in languages such as C++, its semantics can be controlled as well by declaring assignment operation. For example,
virtual DisplayItem& operator=(const DisplayItem&);We may implement this operation to provide either shallow or deep copy semantics. Omitting this explicit declaration invokes the default assignment operator, whose semantics are defined as a memberwise copy.
Q1.12: What is object equality? (Ref: 3.1, p.95)
There are following two meanings in object equality.
In C++, there is no default equality operation, thus we must establish our own semantics by introducing the explicit operators for equality and inequality as part of the declaration. For example,
virtual int operator==(const DisplayItem&) const; int operator!=(const DisplayItem&) const;
Q1.13: What is object life span? (Ref: 3.1, p.96)
The lifetime of an object extends from the time it is first created until that space is reclaimed. To explicitly create an object, we must either declare it or allocate it. There are three kinds of lifetimes:
DisplayItem item1;
DisplayItem item2 = new DisplayItem(Point(75, 75));
new
operator
must be explicitly destroyed with the delete
operator. In other words, the object exists until it is destroyed
explicitly by the delete
operator. Failure to do
delete
leads to memory leaks.
An object by itself is intensely uninteresting. Objects contribute to the behavior of a system by collaborating with one another.
The relationship between any two objects encompasses the assumptions that each makes about the other, including what operations can be performed and what behavior results. Two kinds of object hierarchies are of particular interest in object-oriented analysis and design:
Q2.2: What are links? (Ref: 3.2, p.98)
The term link is defined as a "physical or conceptual connection between objects". An object collaborates with other objects through its links to these objects. State another way, a link denotes the specific association through which one object (the client) applies the services to another object (the supplier), or through which one object may navigate to another.
Here is an illustrated example of several links.
As a participants in a link, an object may play one of three roles:
Q2.3: Example of link? (Ref: 3.2, p.99)
In many different kinds of industrial processes, certain reactions require a temperature ramp, wherein we raise the temperature of some substance, hold it at that temperature for a fixed period, and then let it cool to ambient temperature.
The following is an abstraction of a temperature ramp.
// Number denoting elapsed minutes typedef unsigned in Minute; class TemperatureRamp{ public: TemperatureRamp(); virtual ~TemperatureRamp(); virtual void clear(); virtual void bind(Temperature, Minute); Temperature temperatureAt(Minute); protected: ... };
Then we introduce an abstraction of a temperature control:
class TemperatureController{ public: TemparatureControll(Location); ~TemparatureControll(); void process(const TemperatureRamp&) Minute schedule(const TemparatureRamp&) const; private: Heater heater; .... };
Then let a controller to to carry out the temperature ramp profile.
.... TemperatureRamp growingRamp; TemperatureController rampController(7); growingRamp.bind(250, 60); rampController.process(grawingRamp);
Consider the relationship between the object growingRamp
and rampController
. The rampController
is an
agent responsible for carrying out a temperature ramp, and so uses
the object growingRamp
as a server. This link manifests
itself in the fact that the object rampController
uses
the object growingRamp
as an argument to one of its
operations.
Q2.4: What is visibility of objects? (Ref: 3.2, p.101)
Consider two objects, A and B, with a link between the two. In order for A to send a message to B, B must be visible to A in some manner.
In the previous example, the object
rampController
has visibility to the object
growingRamp
, because both objects are declared within the
same scope, and growingRamp
is presented as an argument
to an operation upon the object growingRamp
.
There are four different way that one object may have visibility to another:
During the analysis of a problem, we can largely ignore issues of visibility, but once we begain to devise concrete implementations, we must consider the visibility across links. How one object is made visible to another is a tactical design issue.
Q2.5: What is aggregation? (Ref: 3.2, p.102)
Whereas links denote client/supplier(server) relationships, aggregation denotes a whole/part hierarchy, with the ability to navigate from the whole (also called aggregate) to its parts (also known as its attributes).
Consider again the example in Q2.3.
rampController
has a link to the object
growingRamp
.
rampController
has an attribute
heater
whose class is Heater
.
Aggregation may or may not denote physical containment. For examples:
There are clear trade-offs between links and aggregation.
Intelligent engineering decisions require careful weighing of these two factors.
The concepts of a class and an object are tightly interwoven, for we cannot talk about an object without regard for its class. However, there are important differences between these two terms.
Whereas an object is a concrete entity that exists in time and space, a class represents only an abstraction the "essence' of an object, as it were.
Booch defines a class as follows:
A class is a set of objects that share a common structure and a common behavior.A single object is simply an instance of a class.
Q3.2: What are class interface and implementation? (Ref: 3.3, p.105)
There is a view that programming is largely a matter of "contracting". The various functions of a large problem are decomposed into smaller problems by subcontracting them to different elements of the design.
This view of programming as contracting leads us to distinguish between the outside view and the inside view of a class.
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.
The interface of a class can be divided into three parts:
Classes, like objects, don not exist in isolation. Rather, for a particular problem domain, the key abstractions are usually related in a variety of interesting ways, forming the class structure of a design.
There are three basic kinds of class relationships:
To capture the above three relationships, most object-oriented languages provide direct support for some combination of the following relationships:
Of these six 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, at which time we begin to discover the general dependencies among abstractions. As we continue our design, and implementation, we will often refine these weak associations by turning them into one of the other more concrete class relationships.
Q4.2: What is class association? (Ref: 3.4, p.108)
The class association denotes a semantic connection of two classes.
For example, in an automated system for retail point of sale, two of
our key abstractions include productions and sales.As shown
here,
we may show a simple association between these two
classes: the class Product
denotes the products sold
as part of a sale, and the class Sale
denotes the
transaction through which several products were last sold.
By implication, this association suggest bidirectional navigation: given
and instance of Product
, we should be able to
locate the object denoting its sale, and given an instance of
Sale
, we should be able to locate all the products
sold during the transaction.
We may capture these semantics in C++ by using what Rumbaugh calls buried pointers.
class Product; class Sale; class Product { public: ... protected: Sale* lastSale; }; class Sale { public: ... protected: Product** productSold; };
Here we show a one-to-many association: each instance of
Product
may have a pointer to its last sale, and
each instance of Sale
may have a collection of
pointers denoting the products sold.
As the above example suggests, an association only denotes a semantic dependency and does not state the direction of this dependency (unless otherwise stated, an association implies bidirectional navigation), nor does it state the exact way in which one class relates to another (we can only imply these semantics by naming the role each class plays in relationship with the other.
These semantics are sufficient during the analysis of a problem, at which time we need only to identify such dependencies. Through the creation of associations, we come to capture the participants in a semantic relationship, their roles, and cardinality.
Q4.3: What is cardinality of class association? (Ref: 3.4, p.109)
The example of Q4.2 introduced a one-to-many
association, meaning that for each instance of the class
Sale
, there are zero or more instances of the
class Product
, and for each product, there is
exactly one sale. This multiplicity denotes the cardinality
of the association.
In practice, there are three common kinds of cardinality across an association:
Q4.4: What is inheritance? (Ref: Glossary)
Inheritance is a relation among classes, wherein one class shares the structure or behavior defined in one (single inheritance) or more ( multiple inheritance) other classes. Inheritance defines an "is-a" hierarchy among classes n which a subclass inherits from one or more generalized superclasses; a subclass typically specializes its superclasses by augmenting or redefining existing structure or behavior.
Q4.5: Example of inheritance? (Ref: 3.4, p.110)
For an example, consider a telemetry system. After space probes are launched, they report back to ground stations with information regarding status of important subsystems (such as electrical power and propulsion systems) and different sensors (such as radiation sensors, mass spectrometers, cameras, ...). Collectively, this relayed information is called telemetry data.
Telemetry data is commonly transmitted as a bit stream consisting of a header, which includes a time stamp and some keys identifying the kind of information that follows, plus several frames of processed data from the various subsystems and sensors. A possible abstraction of this telemetry data is:
class TelemetryData { public: TelemetryData(); virtual ~TelemetryData(); virtual void transmit(); Time currentTime() const; protected: int id; Time timeStamp; };
Then consider the data related to electric subsystems. Because this data is a telemetry data, it can be declared as follows:
class ElectricalData : public TelemetryData { public: ElectricalData(float v1, float v2, float a1, float a2); virtual ~ElectricalData(); virtual void transmit(); float curentPower() const; protected: float fuelCell1Voltage, fuelCell2Voltage; float fuelCell1Amperes, fuelCell2Amperes; };
Here
is the graphical illustration of the single inheritance relationships
deriving from the superclass TelemetryData
.
Q4.6: What is polymorphism? (Ref: 3.4, p.114)
Polymorphism is an concept in type theory wherein a name may denote instances of may different classes as long as they are related by some common superclass. Any object denoted by this name is thus able to respond to some common set of operations in different ways.
Q4.7: Example of polymorphism? (Ref: 3.4, p.114)
Consider again the example in Q4.5. For the class
TelemetryData
, we might implement the member
function transmit
as follows:
void TelemetryData::transmit() { // transmit the id // transmit the timeStapm }
We might implement the same member function for the class
ElectricalData
as follows:
void ElectricalData::transmit() { TelemetryData::transmit(); // invoke superclass function // transmit the voltages // invoke particular to ElectricalData // transmit the amperes }
Suppose that we have an instance of each of these two classes:
TelemetryData telemetry; ElectricalData electrical(5.0, -5.0, 3.0, 7.0);
Now, given the following nonmember function,
void transmitFreshData(TelemetryData& d, const Time& t) { if (d.currentTime() >= t) d.transmit(); }
Then consider the following two statements:
transmitFreshData(telemetry, Time(60)); transmitFreshData(electrical, Time(120));
Each of the above statements executes the transmit
operation which corresponds to the appropriate class.
In the implementation of transmitFreshData
, the statement
d.transmit()
does not explicitly distinguish d
.
This behavior is polymorphism.
Q4.8: Why to use polymorphism? (Ref: 3.4, p.116)
Without polymorphism, the developer ends up writing code consisting of large case or switch statements. This is in fact the litmus test for polymorphism. The existence of a switch statement that selects an action based upon the type of an object is often an warning sign that the developer has failed to apply polymorphic behavior effectively.
Polymorphism is most useful when there are many classes with the same protocols. With polymorphism, large case statements are unnecessary, because each object implicitly knows is own type.
Q4.9: What is the relation between polymorphism and late binding? (Ref: 3.4, p.116)
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 use early or late binding:
Q4.10: What is aggregation? (Ref: 3.4, p.128)
Aggregation relationships among classes have a direct parallel to aggregation relationships among the objects corresponding to these classes.
Let consider again the declaration of the
TemperatureController
in the example of
Q2.3:
class TemperatureController{ public: TemparatureControll(Location); ~TemparatureControll(); void process(const TemperatureRamp&) Minute schedule(const TemparatureRamp&) const; private: Heater heater; .... };
In this example, we have aggregation as containment by values,
a kind of physical containment meaning that the Heater
object does not exist independently of its enclosing
TemparatureController
instance. The lifetimes of these
two objects as intimately connected.
Here is a graphical illustration of the aggregation relationship.
A less direct kind of aggregation is also possible, called containment
by reference. For example, we might replace the private part of
the class TemperatureController
with the following
declaration:
Heater* heater;
In this case, the class TemperatureController
still
denotes the whole, and an instance of the class Heater
is still one of its part, 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.
Containment by value may not by cyclic (that is, both objects may not physically be parts of one another), although containment by reference may be (each object may hold a pointer to the other).
The litmus test for aggregation is that if and only if there exists a whole/part relationship between two objects, we must have an aggregation relationship between their corresponding classes.
Q4.11: What is using? (Ref: 3.4, p.130)
Whereas an association denotes a bidirectional semantic connection, a using relationship is one possible refinement of an association, whereby we assert which abstraction is the client and which is the supplier of certain service.
Let consider again the example in Q2.3. In this
example, the class TemperatureController
is
define as follows:
class TemperatureController{ public: TemparatureControll(Location); ~TemparatureControll(); void process(const TemperatureRamp&) Minute schedule(const TemparatureRamp&) const; private: Heater heater; .... };
The class TemperatureRamp
appears as part of
the signature in member functions process
and
schedule
, and thus we can say that
TemperatureController
uses the services of the
class TemperatureRamp
.
Booch illustrates such a client/supplier using relation as this.
Q4.12: What is instantiation? (Ref: 3.4, p.131)
The instantiation generally means the process of creating instances from classes. However, Booch uses here the word instantiation in the limited semantics as follows:
The instantiation is the process of filling in the template of a generic or parametrized class to produce a class from which one can create instances.
Here, a parametrized class (generic class) is one that serves as a template for other classes - a template that may be parametrized by other classes, objects, and/or operations. A parametrized class must be instantiated (that is, its parameters must be filled in) before objects can be created.
For example, consider the class Queue. Its abstraction can be done by using parametrized class as follows:
template<class Item>; class Queue { public: Queue(); Queue(const Queue<Item>&); virtual ~Queue(); virtual Queue<Item>& operator=(const Queue<Item>&); virtual int operator==(const Queue<Item>&) const; int operator!=(const Queue<Item>&) const; virtual void clear(); virtual void append(const Item&); virtual void pop(); virtual void remove(int at); virtual int length() const; virtual int isEmpty() const; virtual const Item& front() const; virtual int location(const void*); protected: ... };
The following are examples of instantiation:
Queue<int> intQueue; Queue<DisplayItem*> itemQueue;
As seen from the above, instantiation relationships almost always require some using relationship, which make visible the actual classes to fill in the template.
Here is the graphical representation of the instantiation of the above example.
Q4.13: What is metaclass? (Ref: 3.4, p.133)
A metaclass is a class whose instances are themselves classes. Languages such as Smalltalk and CLOS support the concept of a metaclass directly; C++ does not.
In languages such as Smalltalk, the primary purpose of a metaclass is to provide class variable (which are shared by all instance of the class) and operations for initializing class variables and for creating the metaclass's single instance.
Although C++ does not explicitly support metaclasses, its constructor and destructor semantics serve the purpose of metaclass creation operations.
Specifically, in C++ one may declare a member object or member function as static, meaning that the member is shared by all instances of the class. Static member objects in C++ are equivalent to Smalltalk's class variables, and static member functions are equivalent to metaclass operations in Smalltalk.
Here
is the graphical representation of the metaclass of the example in
Q4.5. In this example, newID
is a static member, and new()
is a constructor
in C++.
During analysis and the early stages of design, the developer has two primary tasks:
During these phases of development, the focus of the developer must be upon the outside view of these key abstractions and mechanisms. This view represents the logical framework of the system, and therefore encompasses the class structure and object structure of the system.
In the later stages of design and then moving into implementation, the task of the developer changes: the focus is on the inside view of these key abstractions and mechanisms, involving their physical representation. We may express these design decisions as part of the system's module architecture and process architecture.
Booch suggests the following five meaningful metrics for measuring the quality of an abstraction:
Dog
is functionally cohesive if its semantics
embrace the behavior of a dog, the whole dog, and nothing but the
dog.
Set
,
we should include operations both to remove and add an item.
Neglecting one of them violates the sufficiency.
Q6.2: Where to place operations/methods? (Ref: 3.6, p.138)
Crafting the interface of a class is plain hard work. Typically, we make a first attempt at the design of class, and then, as we and others create clients, we find ti necessary to augment, modify, and further refine this interface. Eventually, we may discover patterns of operations or patterns of abstractions that lead us to invent new classes or to reorganize the relationships among existing ones.
It is common in object-oriented development to design the methods of a class as a whole, because all these methods cooperate to form the entire protocol of the abstraction. Thus, given some desired behavior, we must decide in which class to place the method.
Following criteria are proposed to make a decision of where to place a method:
Note : From Booch's book, it is not clear how to use the above criteria. For details, refer the following paper:
D.Halbert and P.O'Brien, IEEE Software Vol.4(5) 1988, p.74
Q6.3: When to use a free subprogram? (Ref: 3.6, p.139)
We usually choose to declare the meaningful operations that we may perform upon an object as methods in the definition of that objects' class (or superclass). In languages such as C++ and CLOS, however, we may also declare such operations as free subprograms, which we then group in class utilities.
In C++ terminology, a free subprogram is a nonmember function. Because free subprograms cannot be redefined as methods can, they are less general However, class utilities are helpful in keeping a class primitive and in reducing the coupling among classes, especially it these high-level operations involve objects of many different classes.
Q6.4: What types of message passing to be considered in concurrency? (Ref: 3.6, p.139)
In most of the languages we use, synchronization among objects is simply not an issue, because our programs contain exactly one thread of control, meaning that all objects are sequential.
However, in languages that support concurrency, we must concern ourselves with more sophisticated forms of message passing, so as to avoid the problems created if two threads of control act upon the same object in unrestrained ways.
We have found it useful in some circumstances to express concurrency semantics for each individual operation as well as for the object as a whole, since different operations may require different kinds of synchronization. Message passing may thus take one of the following forms:
Q6.5: How to choose relationships among classes/objects? (Ref: 3.6, p.140)
If we decide that object X
sends messages
M
to object Y
, then
directly or indirectly, Y
must be accessible
to X
. Abstractions are accessible to one another
only where their scopes overlap and only where access rights are
granted (for example, private parts of a class are accessible only
to the class itself and its friends).
One useful guideline in choosing the relationships among objects is called the Law of Demeter which is as follows:
The basic effect of applying this law is the creation of loosely coupled classes, whose implementation secrets are encapsulated. Such classes are fairly unencumbered, meaning that to understand the meaning of one class, you need not understand the details of many other classes.
Q6.6: Which to select shallow or deep inheritance? (Ref: 3.6, p.140)
This is a part of the question of Q6.5.
In looking at the class structure of an entire system, there are two kinds of inheritance hierarchy:
The proper shape of a class structure is highly problem-dependent.
Q6.7: Which to select inheritance or aggregation? (Ref: 3.6, p.141)
This is a part of the question of Q6.5.
We must make trade-offs among inheritance, aggregation, and using
relationships. For example, should the class Car
inherit, contain, or use the classes named Engine
and Wheel
? In this case, we suggest that an
aggregation relationship is more appropriate than inheritance
relationship.
In general it is suggested that, between classes A
and B
,
B
may also be viewed as an instance of A
,
B
simply processes one or
more attributes of A
.
Q6.8: How an object is visible to another? (Ref: 3.6, p.141)
This is a part of the question of Q6.5.
During the design process, it is occasionally useful to state explicitly how one object is visible to another. There are four fundamental ways that a object may be made visible to another object:
See the Q2.4 for the same kind of arguments. Especially, the 4th statement in the above is slightly different from the one in Q2.4.