We define physical coupling and some related terms. We discuss the ramifications of physical coupling on C++ software design, stressing the need for limiting physical coupling in large systems. We use as an example a fictitious class Event. Please note that this class is not the class proposed for the CDF event model; it is a toy class, introduced only to illustrate the concepts discuss here in a familiar case.
Physical coupling is the dependence of one component or package upon another, at either compile time or link time. Some of the terms used in discussing physical coupling are listed below. A very complete discussion of physical coupling, its effects, and ways of ameliorating its effects can be found in the book "Large Scale C++ Software Design", by John Lakos.
Since the classes will evolve over time, we would like assure that changes to a class will not cause clients to require recompilation unnecessarily. Furthermore, we would like to be able to test components in the smallest possible setting, to make the testing as easy as possible. Excessive physical coupling, either compile-time or link-time, makes each of these tasks more difficult, thus hindering code maintenance.
In a system with poor physical design, making a tiny change in one class can cause a ripple effect that will result in the need to re-compile large parts of the entire body of software, which would slow development to a crawl. Changes to a class that can cause clients to require recompilation include
In addition, even if compile-time coupling is kept low, poor physical design of the system could involve large link-time coupling so that using or testing a low level class could require linking with a significant number of other classes. In C++, link-time coupling occurs when some part of the system uses the interface or implementation of a class, rather than knowing about the class in name only. Excessive link-time coupling can lead to bloated executable images, which contain much unused code.
Most severe is a cyclic dependency, in which component A depends on component B, which in turn depends on component A. Clearly, larger cycles are also possible. Cyclic dependencies are especially difficult because they prevent any of the elements of the cycle from being used unless all the members of the cycle are linked into the program. Cyclic dependencies will dramatically increase the cost of maintenance for a system.
In the following example, we introduce a fictitious Event class, illustrating how evolution
over time of a class can be expensive if physical design issues
are neglected. Any change in the Event
class's interface or implementation as contained in Event.hh,
causes all clients of Event to require
recompilation.
Event.hh:
// Version 1: Event contains Tracks and Vertices only
// Version 2: added PreshowerHits data and accessor function
// Version 3: added getTracksInVertex accessor
class Event {
public:
Tracks* getTracks() const; // V1
Vertices* getVertices() const; // V1
PreshowerHits* getPreshowerHits() const; // new in V2 (all clients of Event class must be recompiled)
Tracks* getTracksInVertex() const; // new in V3 (1 day later...recompile again)
private:
Tracks* _tracks;
Vertices* _vertices;
PreshowerHits* _psHits; // new in V2
}
If Event.hh needs headers (like Tracks.hh)
for the data it holds, any change in those headers will cause all
clients of the Event class to require recompilation.
Also, if either the header file for Event or
implementation file (Event.cc) needs headers, all
clients of Event must be linked with the object file
(like Tracks.o), even if the client never uses the
class.
class Event {
public:
// Changing the definition of Tracks does not cause the Event class
// or its clients to require recompilation. However, it could cause a link-
// time dependency if a member function of Event (1) uses the Tracks class
// interface or (2) returns a Track by value (not pointer or reference)
Tracks* getTracks1(); // Neither ptr nor ref force inclusion of Tracks.hh.
Tracks& getTracks2(); // Neither causes a link dependency: Event.cc doesn't need Tracks.hh
Tracks getTracks3(); // Doesn't require Tracks.hh.
// Link dependency from return by value in Event.cc
float getATrackPt(); // Causes link dependency due to Event.cc needing Track.hh
float getATrackZ() // Inline function requires Track.hh: Compile & link dependency
{return _trks1->track(1)->z();}
private:
// In member data, pointers and references do not cause compile-time and
// link-time coupling, but containing by value typically does.
Tracks* _trks1; // Doesn't require Tracks.hh
Tracks& _trks2; // Doesn't require Tracks.hh
Tracks _trks3; // Requires Tracks.hh
RefCntPtr<Tracks> _trks4 // Requires Tracks.hh due to template instantiation
VertexList _vert; // Requires Vertex.hh (which requires Tracks.hh)
}
Member functions of the Event class can return
objects by value, pointer or reference, without causing a
compile-time dependency, but returning an object by value (like Tracks)
requires that the Event link with the component
object file (Tracks.o). Inline functions typically
expose implementation detail in the header file thus causing
compile and link dependency.
Member data contained by pointer or reference causes no compile or link dependency, but containing by value causes both a compile and link dependency. In addition, containing by value can cause a cascade of dependencies to many other classes.
This page last updated: 15 Mar 1999 03:02 PM
This page kept by: Marc
Paterno