Re: Tell, Don't Ask



Responding to Daniel T....

Having said that, I think there are trade-offs that can still be made about the responsibilities of external clients, at least in some situations. Let's assume my example where values must be set in pairs but an arbitrary order prevails. We can add two flags, newHighProvided and newLowProvided, along with two temporary value stores, newHighValue and newLowValue, to Range. Then we might have:

def reset(self)
self.newHighProvided = FALSE
self.newLowProvided = FALSE

def setHigh(self, value)
"require self.newHighProvided = FALSE"
"require self.newLowProvided = FALSE ||
(self.newLowProvided = TRUE &&
value > self.newLowProvided)
self.newHighValue = value
self.newHighProvided = TRUE
if (newLowProvided)
self.setValues()


That precondition forces the client to (a) keep track of what order the messages are sent to Range and (b) help ensure the Range object's invariants. The whole point was for Range to ensure its own invariant. I don't think it's right to foist that off on Range's client. "Tell, don't Ask" remember? With the above, the client of range must first ask the object if it's OK to call a particular function, and if the value being passed in is OK.

No, the purpose of the flag complexity is to eliminate any external knowledge of ordering for (a). (There is a symmetric precondition for setLow that allows either one to be invoked first.) The context simply needs to ensure that values are provided in pairs.


Yes, once one is called, it cannot be called again until the other is called. Which means the object's client(s) must track which was called to make sure the order is correct. Picture a system where a Range object is being shared by objects in two different threads... Another of Hunt & Thomas' views, "Always Design for Concurrency: Allow for concurrency, and you'll design cleaner interfaces with fewer assumptions." Obviously, the interface you outline would be hell in a concurrent environment.

OK. But there is a big difference between a contract that requires the context to provide values in a specific order and a contract that requires the context to provide pairs of values in any order.

I think the concurrency argument is something of a straw man. Here my solution was constrained by separate setHigh and SetLow interface methods in the original example. If those are invoked from different contexts, then there will /always/ be an issue for concurrent processing if values must be consistent as pairs.

In addition, the concurrency problem will be solved outside the context of Range in all circumstances anyway. That's because Range is just a data holder and it have no intrinsic problem space behaviors. So the interface setters will be invoked synchronously by behaviors in the external context. Those behaviors, though, collaborate with an asynchronous communication model in the OOA/D. It is there that the constraint of providing pairs will have to be honored (which is actually pretty easy to do with interacting state machines).

However, an asynchronous communication specification can /always/ be implemented in a concurrent environment. So concurrency is not really a problem here -- that's why OOA/D uses an asynchronous behavior communication model.

def setLow(self, value)
// symmetric with setHigh

def setValues
"invariant self.newLowValue < self.newHighValue"
self.low = self.newLowValue
self.High = self. newHighValue

...here acts as a correctness assertion on the overall processing. That is, in the hypothesized problem there could be different clients for setHigh and setLow. Then ensuring consistency in the setup of a Range potentially depends upon complex collaborations in the external context among multiple objects.

How does one capture that via DbC? The most logical place is where those factors come together in Range. The invariant, indeed, represents an intrinsic characteristic of the notion of 'range'. But it is also a constraint one how the client context must work. IOW, the client context must share the same semantic view of Range's responsibilities that the invariant enforces.

Bottom line: I disagree somewhat with the implication that clients only care about preconditions. Both the discipline of the context collaborations and the Range invariant are rooted in the same shared semantics of 'range'. Thus the clients must obey the invariant in their own way just as much as Range. Coming full circle back to interfaces, that is manifested (in part) by the setHigh and setLow interface methods.


Well, therein lies the problem as I see it. It should be solely the job of Range to manage it's invariant, not the clients. So what interface would best express that?

The only *place* to express it is within the preconditions of Range's methods because that is where the clients responsibilities are established. Range objects should not force their clients to do their job for them. Maybe the concept I outlined isn't correct, or maybe it just isn't complete. Any ideas?

Indeed, this is where we disagree. I see a difference between Where one enforces an invariant and How it is enforced. DbC assertions provide a convenient mechanism for specifying enforcement. The 'require' in Range enforces an invariant (consistent pairs of values) that is implicit in the semantics of a 'range'. Both the DbC paradigm and Range semantics conspire to make Range the right place to enforce that invariant.

But the external context is where consistency is actually implemented. That is very clear in the point I made above about synchronous data access and asynchronous behavior communications facilitating concurrency. One /can't/ ensure consistency in Range for a concurrent environment because it has no problem behaviors. So the implementation of the consistency that the invariant specifies and enforces must be outside Range.

At another level, I see no way around the external context understanding the pair constraint inherent in 'range' semantics. Regardless of the interface -- providing both in a single interface setter just makes compliance transparent -- the client context must honor the 'range' semantics when collaborating with it.

At yet another level I can muddy the waters further. B-) One can conceive of an application where one naturally provided, say, a succession of High values without a new Low value. An example might be some sort of What If simulation. The notion of a consistent pair of values still applies. However, the internal Range implementation I suggested would be broken unless the client repeated setting the low value redundantly. (One would have that problem of client redundancy if the interface specified both values in a single setter.) The point is that the client needs to understand that and supply Low values as pairs redundantly.

This introduces a kind of quasi-LSP argument about the reusability of Range. The root problem lies in the fact that internally implementing the invariant indirectly depends upon whether Range can "reuse" values from previous pairs. My point here is that one really needs to spell that constraint out in the client contracts as well. Alternatively one could loosen the constraints on Range somehow, such as your original example where all one cared about is High > <current> Low and Low < <current> High. That effectively homogenizes the Range semantics and puts the ball back in the client's court for making sure Range is used properly.

IOW, the semantics needed for Range can change depending upon the context of usage. Thus the client context, interface, and Range implementation must all play together optimally when defining the Range semantics and corresponding DbC conditions, including invariants.

FWIW, the last sentence is why I don't like facile mantras like 'tell; don't ask'. I see design as an optimization of collaborations, responsibilities, and implementations that is based on broader principles. For example, I think the dominant issue at the OOP level is minimizing what client and service know about each other, which is what drives dependency management. At the OOA/D level there are several much more subtle trade-offs to be made between things like cohesion, encapsulation, emulating problem space structure, etc., etc.

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

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • Re: Message-based vs. method-based interaction [was: Re: LSP and subtype]
    ... interface is always the method signature. ... We can name it for the owner context of what it does or we can name it for the client context, but we can't do both. ... overlaying some sort of developer structure on the 3GL syntax. ...
    (comp.object)
  • Re: Creating a "toy" OO/AO language...
    ... That is a major difference between the OO paradigm and procedural/functional paradigms where behavior always dominates the construction. ... In an OO context there are two ways to implement a closure in the functional programming sense of a method that operates on a bounded set of variables. ... The only object that should have an interface to the strategy ... In the OO paradigm one tries to minimize that by limiting it to client and service through peer-to-peer collaboration by eliminating a hierarchical call chains. ...
    (comp.object)
  • Re: implementing roles in OOP......
    ... > announcement but the Y method name makes no sense in that context. ... > priori knowledge of the structure there is no way for the client to know ... I can imagine an interface that would allow adding Composites ... The example given is exactly the sort of thing that does happen, ...
    (comp.object)
  • Re: MERGE as the imperative form of aggregation
    ... Assume the below executes on a client machine, ... and the relvar T is on a distinct server machine. ... Introducing context sensitivities into expression evaluation rules ... ambiguity in view updates. ...
    (comp.databases.theory)
  • Re: Pure client-side javscript database?
    ... the individual asking the question in their single context. ... in the current browser instance and a respondent assumes the question ... >>> that the client may download an application from a server ... >>> server, but the APPLICATION may or may not be. ...
    (comp.lang.javascript)