From: Caleb Clausen on
On 2/15/10, Intransition <transfire(a)gmail.com> wrote:
> On Feb 15, 4:24 am, Caleb Clausen <vikk...(a)gmail.com> wrote:
>
>> When people say things like this, it makes me think there ought to be
>> a way to do it by using RubyMacros. So, I tried to rewrite Tom/Brian's
>> code as macros... I think I can get rid of the need to call extend or
>> use method_missing/proxies. But I had to use a feature of RubyMacros
>> that I haven't invented yet (motivation!) and I may be missing the
>> point (esp as I didn't watch the movie). Since I don't know if it
>> would even work, I'm reluctant to post the example I came up with,,,
>> but if anyone really wants to see it, I will.
>
> Personally I think Mr. Mittag is overstating the issue. I haven't seen
> anything yet to indicate that DCI is beyond implementation in regular
> Ruby. But then maybe I am misunderstanding some key elements --I would
> love to know. In any case, feel free to show us your code, even if it
> is just an exercise in what can be done with RubyMacros.

Since writing that, it occurred to me that the same effect could be
achieved with regular old eval. There's always an eval equivalent to
any use of macros, but in this case the result is actually pretty
clean. So, here's my eval-based version: http://gist.github.com/304558

What I did differently: creation of the context subclass I consigned
to a utility method, since that appears to be boilerplate code. The
role class has disappeared entirely, replaced by hashes. The result is
less orthodox than the classes-and-modules approach used by brian and
yourself... but I think the minimal amount of code that users of this
context library have to write is particularly nice. I may have made
too many static assumptions, such as:

Balance::Transfer#transfer always just calls #transfer on its child Roles

Those roles in turn simply forward the #transfer message to some
other method(s)
(increaseBalance and decreaseBalance in this case)

Please let me know what you think of this.

From: Brian Candler on
Caleb Clausen wrote:
> Those roles in turn simply forward the #transfer message to some
> other method(s)
> (increaseBalance and decreaseBalance in this case)
>
> Please let me know what you think of this.

I think it's more limiting that what DCI is supposed to offer; I think
you are supposed to inject *new* methods into the underlying objects to
help them fulfil their roles, rather than just mapping existing ones.
That is, the role contains extra logic which in normal OOP might pollute
the model, and DCI helps separate it out.

I've been going through Trygve's Gantt planner example documented in
http://heim.ifi.uio.no/~trygver/2009/bb4plan.pdf

Whilst the code appears incomplete (it relies on a base class defined
earlier in the book), and given also that I don't grok Smalltalk, I've
still picked up a few things. Here is one example:

Frontloader>>frontloadFrom: startWeek
AllActivities do: [:act | act earlyStart: nil].
[ Context reselectObjectsForRoles.
Activity notNil
] whileTrue:
[ Activity earlyStart: startWeek.
Predecessors do:
[ :pred |
(pred earlyFinish > Activity earlyStart)

In the PDF, you'll see that "AllActivities", "Context", "Activity" and
"Predecessors" are roles, and are underlined to highlight them - a bit
of a weakness IMO that they are not clear in the syntax.

Anyway, the Frontloader is a separate class which is (as far as I can
see) somehow 'mixed in' to the FrontloaderCtx context object using
roleStructure magic. But if it were a single object I think it might
look roughly like this:

class FrontloaderCtx
attr_reader :all_activities, :activity, :predecessors

def initialize(model)
@model = model
end

def reselect_objects_for_roles
@all_activities = @model.all_activities
@activity = @all_activities.find { |act|
act.early_start.nil? && !@model.predecessors_of(act).find { |pred|
pred.early_start.nil? }
}
@predecessors = @model.predecessors_for(@activity)
end

def frontload(start_week)
while (reselect_objects_for_roles, activity)
...
end
end
end

which is probably not too much different to how you'd write a
front-loader "controller", except I'd be inclined to use local variables
for the 'roles' rather than instance variables.

However it's clear from this that the assignment of objects to roles is
something which it intended to change during execution of a single
method, since the whole algorithm relies on
"reselect_objects_for_roles".

I still haven't achieved enlightenment as to what is new or different
about "DCI". The view contexts might provide meatier examples. I did
actually start to translate the whole lot virtually line-by-line into
Ruby, but got a bit stuck on the UI side because I've not done any Ruby
UI programming. It's probably possible to hook Tk in, but I don't fully
understand what's going on in the Smalltalk yet.

Regards,

Brian.
--
Posted via http://www.ruby-forum.com/.

From: Caleb Clausen on
On 2/15/10, Brian Candler <b.candler(a)pobox.com> wrote:
> Caleb Clausen wrote:
>> Those roles in turn simply forward the #transfer message to some
>> other method(s)
>> (increaseBalance and decreaseBalance in this case)
>>
>> Please let me know what you think of this.
>
> I think it's more limiting that what DCI is supposed to offer; I think
> you are supposed to inject *new* methods into the underlying objects to
> help them fulfil their roles, rather than just mapping existing ones.
> That is, the role contains extra logic which in normal OOP might pollute
> the model, and DCI helps separate it out.

So, you're saying that this method, which I had optimized away:

class Balance::TransferDestination < Role
def transfer(amount)
increaseBalance(amount)
puts "Tranfered to account #{__id__} $#{amount}"
end
end

should be able to contain arbitrary amounts of logic?

Let me think about this some more. Maybe I'll try again.

> I still haven't achieved enlightenment as to what is new or different
> about "DCI". The view contexts might provide meatier examples. I did

I have the feeling this is one of those theoretical things that seems
really complicated but once you understand it its actually quite
simple.

From: Brian Candler on
Caleb Clausen wrote:
> So, you're saying that this method, which I had optimized away:
>
> class Balance::TransferDestination < Role
> def transfer(amount)
> increaseBalance(amount)
> puts "Tranfered to account #{__id__} $#{amount}"
> end
> end
>
> should be able to contain arbitrary amounts of logic?

I think so. Otherwise you end up putting all the logic about
*transferring* money inside the Balance object, which really should just
be a dumb model which maintains a balance.

> I have the feeling this is one of those theoretical things that seems
> really complicated but once you understand it its actually quite
> simple.

I've not yet seen a clear (to me) exposition of what DCI actually
*means* in practice. But then I could say the same about AOP.
--
Posted via http://www.ruby-forum.com/.

From: James Coplien on

> Then at the end, it says that an account isn't really an object at all -
> but all the previous code has shown it as a concrete object (e.g.
> Account.find(id)). So an example of what an account role *should* look
> like in code would be good.

Such code has been posted in the past on object-composition, to which
you are subscribed, Brian. You're welcome to re-post it here.

Indeed, an Account is not an object, any more than a predecessor is an
object in the front loader example. It is a role. Remember, the D in DCI
stands for data. In architecture, we are trying to separate many things
from the data. MVC separates the data (the representation of
information) from user interaction. DCI separates use case logic from
domain logic.

The general approach to DCI is that objects should be pretty dumb. They
are just-barely-smart data, and are usually primitive. Over the years we
have been taught that objects should be smart and that their APIs should
reflect what goes on in the use cases. That creates several problems.
One problem is that rapidly changing use-case level logic is mixed in
the same interface with slowly-changing domain interfaces. Another is
that classes don't provide natural boundaries for the delineation of
mental models of algorithms (as DCI used to call them when it was DCA)
or interactions.

An account is a collection of use cases. It is in the "I" part of DCI,
not in the "D" part. If you look at real banking software, the real
objects are transaction logs and audit trails. They become re-configured
in interactions on every use case, where the use cases are at the level
of a Context object called an account. Except for its housekeeping
references that set up the current role / instance binding at the audit
trail and transaction level, the account is stateless. Your bank account
is not a number sitting in memory or sitting on a disk somewhere,
anymore than your money is sitting in a bag on a shelf in a bank
somewhere. It is a computation: a use case. In DCI, we encapsulate those
in Contexts.

In my talk, I catered to the usual kind of example used by consultants
and university professors in talking about object-oriented programming,
where they apply the little white lie of an account being an object.
Later in the talk I introduce the concept of an account as a context.
This is a recent and rather advanced concept in DCI.


I can see from the thread below that it was too much for the posters in
this thread, and that the posters were unable to correlate that example
with the description in the Artima article. The reason this is a bit
advanced is that it comes from the design thinking that Trygve and I
have put into DCI, rather than the nerd-level stuff. It doesn't cater to
UML-shaped heads, or even to the way that most people characterize
object-oriented programming. It is one of the more difficult ideas in
DCI, and it is the one that most people trip on. Most people have so
much trouble fitting into their mental model that they just say that it
is wrong, or stupid. It's O.K. if you feel that way: new paradigms are
hard, and it will take a while to unlearn old ways and to learn new.

The best way is to keep in dialog and to keep trying things out. Try to
get above the code level and think about this from a design perspective
(but still with the code in the back of your mind, by all means). You'll
hopefully get to a turning point where you see programming in a totally
different way. If you haven't gotten to that point yet, you probably
have internalized only the nerd part of DCI. That's a good start. But as
one poster here said: it's not about traits, it's not about injection,
and it's not about aspects, but about something higher level. It's about
thinking in objects while being able to separate the algorithm into
something manageable and understandable.
--
Posted via http://www.ruby-forum.com/.