Re: Passing by reference



Responding to Veeneman...

Is passing a method argument by reference inherently dangerous?

It depends upon the reason one is passing the reference. One can't instantiate all relationships via a constructor so there will always be a need for passing an object reference to instantiate conditional relationships of relationships where participation varies depending on context. So passing an object reference solely to instantiate a relationship is not Bad simply because it is sometimes unavoidable.

However, passing object references is generally not a good idea in any normal solution collaboration. Consider:

1 R1 1 1 R2 1
[A] --------------- [B] ---------------- [C]
+ doIt()

Let's suppose B.doIt() implements a basic behavior responsibility for [B] that is triggered from [A]. Let's also suppose that B.doIt() needs to collaborate with (send a message to) [C] in the course of the solution.

If an A passes a C as an argument to B.doIt(), that A is essentially instantiating a temporary relationship between B and C. To do that A needs to know what the rules and policies are for participation in the R2 relationship in order to provide the correct C.

Usually the rules and policies for object and relationship instantiation are orthogonal to the rules and policies that drive solution collaborations. IOW, instantiation is about Who participates while collaboration is about When they collaborate and What the do when they collaborate. Thus any collaboration over R2 is a personal matter between [B] and [C] that [A] does not necessarily know anything about.

Even if [A] logically does understand the context for instantiating R2, that is really serendipity. In theory any object in the application might have that responsibility. More important, it may change when requirements change in the future. That is, future maintenance may affect who instantiates R2 but not the basic collaboration between [A] and [B] that navigates R1.

To see a more practical example of why that is bad one needs to look at a code implementation of the relationships. Suppose we combine instantiation and collaboration:

class B
{
public:
void doIt(C*);
}

A::caller()
{
...
myB->doIt(myC);
}

Now suppose one changes one's mind and decides that a [D] object should instantiate the R2 relationship. Clearly both A::caller and B::doIt must change. One probably gets something like:

class B
{
private:
C* myC; // implement R2
public:
void setC (C* c) { myC = c;}; // instantiate R2 called by D
void doIt(); // original behavior; navigates R2.
}

I submit that all this change is doing is implementing proper separation of concerns between instantiation and collaboration _that should have been done in the first place_. If [B] had been implemented this way originally then we would have had

A::caller
{
...
myB->setC (myC);
myB->doIt();
}

The key issue is what happens when we change our mind and decide D needs to instantiate R2. To do that we move the call to B::setC from A::caller to some method in [D] where the instantiation rules are now understood.

However, the collaboration between [A] and [B] is unaffected. We don't touch that at all. More important, the interface to [B] is not affected by the change; the same B::doIt() and B::setC() responsibilities are invoked. That is, they don't care who triggers them. So the primary benefit of separating the concerns of instantiation and collaboration is that we have reduced the scope of software change for the requirements change (i.e., [B] is not touched in any way).


I develop in the .NET environment. For a long time, I have avoided what I consider to be the dangers of passing method arguments by reference, where the method changes the object argument. The biggest danger, IMHO, is that you can't tell by looking at the calling object whether the argument was changed by the method:

MyClass.MyMethod(myObject);

So, at the first step in a method, I have deep-copied any arguments passed in that the method will modify, then passed the modified object back as a return value:

myObject = MyClass.MyMethod(myObject);

One of the advantages of this approach is that it forces methods to limit themselves to doing just one thing.

Alas, I don't think this helps. The receiver can still do anything it wants with the object as part of the R2 relationship, which may result in something the caller doesn't expect.

In the OO paradigm we basically ignore that by striving to make the caller completely indifferent to /whatever/ the receiver does. IOW, we make sure any collaboration between the receiver and the object is a personal mater between them that the caller knows nothing about. Per the point above, [A] should not care or know about what [B] does with [C] in the privacy of its own home.

In addition, we ensure that decoupling by separating message from method so that the message simply announces something the caller did. Then the caller only cares about its responsibilities, not what someone else's are. So long as problem space abstraction has yielded logically indivisible behavior responsibilities at the objects' level of abstraction that works quite well.

For this reason behavior responsibilities generally do not return values to the caller in a well-formed OO application. That's because any such return implicitly creates a dependency in the caller on what the receiver did (i.e., that the caller depends on the receiver returning the /right/ thing with respect to its own requirements). [Obviously knowledge accessors do return values but they are treated quite differently in OOA/D and are highly constrained.]

<aside>
Your concerns reflect a somewhat different issue. Passing object references can be a slippery slope because it encourages carelessness, especially when references are passed through long call sequences before they are accessed. There are various degrees of coupling in collaborations through argument passing:

no arguments. This is the most benign because the only possible thing that can go wrong is that the message is sent at the wrong time.

data-by-value arguments. This is also pretty benign because nothing the receiver does with the arguments can affect the caller.

behaviors (e.g., aplets). This is potentially a problem because the behavior may have unexpected side effects for the caller if its results are affected parametrically by receiver state variable values. However, at least the caller should know what the behavior does.

data-by-reference arguments. Now we can have a potentially major problem because the receiver can change the values that the caller uses in unexpected ways.

Objects-by-reference. Now we have opened a real Pandora's Box because the receiver can modify any attributes or invoke any behaviors, including those the caller doesn't expect, such as deleting the object.

I must emphasize that I am talking about collaborations here. Dealing with side effects is really a matter of properly connecting the dots of the solution collaborations in the right order with messages. One can do that rigorously (albeit tediously) in a formal manner using DbC by matching the preconditions for executing a behavior to the postconditions that prevail after other methods execute to determine who should send a given message to trigger the behavior. The conditions being matched include both the solution sequence and any constraints on timely updating the state variables (attributes). IOW, the messages are daisy-chained such that all the preconditions are satisfied for every behavior method in the application.

However, that depends upon good encapsulation, logical indivisibility, implementation hiding, separation of message and method, and all the rest of that good OOA/D stuff. In particular in this context, the notion of separating the concerns of instantiation from those of collaboration very neatly gets around most of the coupling problems because one is not tempted to pass object references as part of collaborations in the first place.

Perhaps more important, the mindset of self-contained responsibilities and messages as announcements prevents one from getting careless about passing references around and forces one to think about How and When relationships need to be instantiated individually.
</aside>


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • Re: Singletons
    ... There are four basic ways to implement a relationship: embedding an object in the implementation of another object; employing a referential pointer; passing an object reference as a message argument; and using an RDB-style search of instances by explicit identifier. ... Relationships are always implemented and navigated when addressing collaboration messages. ... By modifying the context I meant that one defines the solution flow of control differently so that the instantiation can be done in one place rather than in several. ...
    (comp.object)
  • Re: Is Double Dispatch really object-oriented?
    ... So generally one does not want to pass object references as part of the data packet of collaboration messages. ... That's because instantiation is about Who participates in collaborations while the collaborations themselves are about What happens and When it happens. ... Because the rules and policies that govern instantiation are very likely to be different than the rules and policies that govern routine collaboration, one prefers to encapsulate those rules and policies separately. ...
    (comp.object)
  • Re: C++ design question
    ... > responsibilities, only one of which was to invoke Bar.doIt. ... > and policies for object and relationship instantiation (object ... > policies ensures that the developer thinks about them. ... > concern that leads to using references really isn't a concern because ...
    (comp.object)
  • Re: Yet another design question, C++ oriented : better approach
    ... Do you have an author you read who says that references are ... > In OOA/D relationship ends are either unconditional or conditional. ... > the developer's responsibility to ensure that referential integrity is ... > instantiation and navigation of relationships. ...
    (comp.object)
  • Re: Yet another design question, C++ oriented
    ... Do you have an author you read who says that references are ... In OOA/D relationship ends are either unconditional or conditional. ... is the developer's responsibility to ensure that referential integrity ... instantiation and navigation of relationships. ...
    (comp.object)