Re: Tell, Don't Ask



Responding to Daniel T....

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.


Please, don't constrain yourself that way. One of the main points I've been trying to make is that "Tell don't Ask" style programming creates classes with different interfaces than procedural programming.

Which I also made in my original post.

Let me ask this way. If you needed to implement a class with the constraint two queries: getLow() & getHigh() and an invariant getLow() < getHigh(), what commands would you provide?

If that is all of the semantics of Range that is relevant to the problem in hand, then I would probably provide setValue() and let Range determine whether its internal High or Low gets set based on the invariant.

But if the problem requires that values be provided in consistent pairs, one is in a different ball game. That makes the semantics of Range more complex. That will affect the interface (e.g. setValues (value, value)) and the invariants (i.e., one will have to capture the notion of consistency _among the values_ somehow). And if the context must supply the values separately, that changes the interface again.

So I think the real issue here is that interfaces get defined to serve client needs. One shouldn't arbitrarily define a service semantics and hard-wire an interface to that definition. IOW, the service semantics will be whatever the solution context needs to solve the problem and the interface will reflect that. One then specifies enforcement of that semantics with DbC conditions and provides a service implementation that is consistent with both the DbC constraints and the interface.

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.


This tells me that the example is too low level. How about this:

class Predator:
def sneak(self, pray):
if ( pray.is_edible() ):
pray.flee()

compare that to:

class Predator:
def sneak(self, pray):
pray.predator_nearby( self )

The latter is an example of "Tell, don't Ask", the former is not...

This is an example of why I don't like such mantras. It would never occur to me to even think about "tell; don't ask" here. B-) That's because I see the issues as more fundamental so I go there directly rather than to some distillation in a mantra.

I see the first example as a blatant application of procedural design (Do This). Predator::sneak is a high level function in a functional decomposition tree that is hard-wiring a sequence of operations in its implementation. IOW, it knows exactly what should happen next in the overall solution and who does it.

The second example is classic OO separation of message and method through peer-to-peer collaboration. The predator makes an announcement of what it is doing (I'm sneaking) and some object that cares (Prey) responds.

[The need to designate Prey as the recipient of the announcement in the OOPL code of Predator's implementation is an unfortunate side effect of the 3GL compromises with procedural message passing. In the OOA/D that is not necessary. That sort of muddying is why using OOPL code for these sorts of esoteric discussions is a disadvantage.]

*************
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: Static vs. Dynamic typing
    ... > If you call methods with an expectation of their semantics (ie: ... > GunFighter interface? ... I want a type which specifies exactly that handful and no ...
    (comp.object)
  • Re: Static vs. Dynamic typing
    ... > If you call methods with an expectation of their semantics (ie: ... > GunFighter interface? ... I want a type which specifies exactly that handful and no ...
    (comp.programming)
  • Re: Interface freeloading on a superclass - is it good practice?
    ... > Who's talking about changing any method signatures? ... > the subject of any interface contracts written by the same authors of the ... any future changes to the *precise semantics* of the methods ... Because Sun don't know or care about your interface's rigid semantic ...
    (comp.lang.java.programmer)
  • Re: Classes as units of reuse
    ... One can see this in something as simple as a String class where different libraries will provides somewhat different interfaces for basically the same semantics. ... This is manifested during reuse when a client in a new reuse context wants to access the object semantics using a different interface than the one provided by the object's class. ... However, such overhead is usually not excessive at larger scales, such as subsystems because it is small compared to the subsystem functionality. ... The high level view enabled us to deal with high level control in a quite elegant fashion because at that level of abstraction we didn't care what the individual data element values were; we just needed to manipulate the aggregate. ...
    (comp.object)
  • Re: Why Delphi is not More Popular
    ... because of Delphi's strict syntax with interface and ... enforces better code than C++ where you can do ... reports on it, so maybe... ...
    (borland.public.delphi.non-technical)