Booch Method

Chapter 3. Classes and Objects


Questions List

1. The Nature of an Object

  1. What is an object?
  2. What is the meaning of state of an object?
  3. What is the meaning of behavior of an object?
  4. What is operation of an object?
  5. What are class utilities?
  6. What are roles and responsibilities?
  7. What is the relation between object and state machine?
  8. What is the meaning of identity of an object?
  9. What is the structural sharing?
  10. What is object copying?
  11. What is object assignment?
  12. What is object equality?
  13. What is object life span?

2. Relationships among Objects

  1. What kinds of relationships are there among objects?
  2. What are links?
  3. Example of link?
  4. What is visibility of objects?
  5. What is aggregation?

3. The Nature of a Class

  1. What is a class?
  2. What are class interface and implementation?

4. Relationships among Classes

  1. What kinds of relationships are there among classes?
  2. What is class association?
  3. What is cardinality of class association?
  4. What is inheritance?
  5. Example of inheritance?
  6. What is polymorphism?
  7. Example of polymorphism?
  8. Why to use polymorphism?
  9. What is the relation between polymorphism and late binding?
  10. What is aggregation?
  11. What is using?
  12. What is instantiation?
  13. What is metaclass?

5. The Interplay of Classes and Objects

  1. What the role of classes and objects in analysis and design?

6. On Building Quality Classes and Objects

  1. How to measure the quality of an abstraction?
  2. Where to place operations/methods?
  3. When to use a free subprogram?
  4. What types of message passing to be considered in concurrency?
  5. How to choose relationships among classes/objects?
  6. Which to select shallow or deep inheritance?
  7. Which to select inheritance or aggregation?
  8. How an object is visible to another?


Note:


1. The Nature of an Object

Q1.1: What is an object? (Ref: 3.1, p.83)

An object has:

The structure and behavior of similar objects are defined in their common class.
The term instance and object are interchangeable.

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:

Q1.5: What are class utilities? (Ref: 3.1, p.89)

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:

The unique identity (but not necessarily the name) of each object is preserved over the lifetime of the object, even when its state is changed. This is just like the Zen question about river: is a river the same river from one day to the next, even though the same water never flows through it?

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:




2. Relationships among Objects

Q2.1: What kinds of relationships are there among objects? (Ref: 3.2, p.97)

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.

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.




3. The Nature of a Class

Q3.1: What is a class? (Ref: 3.3, p.103)

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.




4. Relationships among Classes

Q4.1: What kinds of relationships are there among classes? (Ref: 3.4, p.106)

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:

  1. generalization/specialization ("is a" relationship)
  2. whole/part ("part of " relationship)
  3. association (some semantic dependency among otherwise unrelated classes)

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:

  1. One-to-one
  2. One-to-many Many-to-many

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++.




5. The Interplay of Classes and Objects

Q5.1: What the role of classes and objects in analysis and design? (Ref: 3.5, p.135)

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.




6. On Building Quality Classes and Objects

Q6.1: How to measure the quality of an abstraction? (Ref: 3.6, p.136)

Booch suggests the following five meaningful metrics for measuring the quality of an abstraction:

  1. Coupling
    Coupling is defined as the measure for the strength of association established by a connection from one class to another. Strong coupling complicates a system since a class is harder to understand, change, or correct b itself if it is highly interrelated with other class.
    Complexity can be reduced by designing system with the weakest possible coupling between classes. However, 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.
  2. Cohesion
    Cohesion measures the degree of connectivity among the elements of a single class or object. The most desirable form of cohesion is functional cohesion, in which the elements of a class all work together to provide some well-bounded behavior. Thus, the class Dog is functionally cohesive if its semantics embrace the behavior of a dog, the whole dog, and nothing but the dog.
  3. Sufficiency
    Sufficiency means that the class captures enough characteristics of the abstraction to permit meaningful and efficient interaction. For example, if we are designing the class Set, we should include operations both to remove and add an item. Neglecting one of them violates the sufficiency.
  4. Completeness
    Whereas sufficiency implies a minimal interface, a complete interface is one that covers all aspects of the abstraction. A complete class is thus one whose interface is general enough to be commonly usable to any client. Completeness is a subjective matter, and it can be overdone.
  5. Primitiveness
    Since many high-level operations can be composed from low-level ones, we also suggest that classes be primitive.

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,

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.