Specific MVP troubles (Long)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
41 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Kerry Kilbride
On Thu, 21 Dec 2000 13:34:44 -0000, "Ian Bartholomew"
<[hidden email]> wrote:

>
>Out of interest - is anone reading these?
>

Yes - rapty, appreciatively.  Thanks to you and to Phil Lewis for
starting it and for everyone else for contributing.


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Costas Menico-2
In reply to this post by Ian Bartholomew-3
>
>Out of interest - is anone reading these?
>
Great stuff.. I am printing them out and carrying them with me.....


Costas


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew
Costas and all the others who replied,

> >Out of interest - is anone reading these?
> >
> Great stuff.. I am printing them out and carrying them with me.....

Thanks for all the responses, it's nice to know the time and effort wasn't
wasted.

As it has proved useful I might extend the Library theme (as long as Phil
doesn't mind his idea being hi-jacked) to illustrate a few more Dolphin/MVP
concepts. I'll try and use the same format, extend the previous version in
some way with (extensive) comments for each changed method trying to explain
what I changed and why. I assume that's the bit that everyone finds helpful?

I should also mention (as I have this mental picture of other Dolphin users
sitting there shaking their heads and muttering "I wouldn't have done it
like that") that there isn't just the _one way to do most of this stuff.  If
someone can see a better way of doing something, or for some reason just
doesn't like the way I did it, then please share it with the rest of us. I'm
still learning as well <g>

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

plewis66
> As it has proved useful I might extend the Library theme (as long as
Phil
> doesn't mind his idea being hi-jacked)

Take it away, boss!

Maybe a I could suggest some of my other requirements. I may not have
to write this after all :-)

Actually, the whole thing started because I have about 150 of my
personal computing books in my office, and poeple regularly borrow
them. I wanted to keep track of who has what. I could have used Excel,
or Access, but that would have been boring!

If I can make a suggestions, it is precisely because there is so much
explanation that what you are writing is so useful. I think I already
said that a lot of tutorials assume the reader is a beginning
programmer, but, for me, you have hit a perfect level. I _want to know
how the event mechanism works, etc.



Sent via Deja.com
http://www.deja.com/


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Andy Bower
In reply to this post by Phil Lewis-2
Phil,

Sorry, I haven't been able to reply to this thread; I've been away for a few
days. Still, from what I've read so far I couldn't have done any better than
Ian and the others have done with their explanations. Also sorry about the
typo in Personal Money we'll fix that in PL2.

Best regards,

Andy Bower
Dolphin Support
http://www.object-arts.com

---
Visit the Dolphin Smalltalk Wiki Web
http://www.object-arts.com/wiki/html/Dolphin/FrontPage.htm
---

"Phil Lewis" <[hidden email]> wrote in message
news:91tnn8$dc2$[hidden email]...

> <HUGE SNIP>
>
> > Ian:
> >
> > Out of interest - is anone reading these?
>
> Oh, yes, I'm readsing them OK.
>
> Earlier, I just overlooked the method for OnLibraryStatusChanged. I was
> reading in a hurry.
>
> Now I am at home, I have hard copy, and I going to read them toroughly.
>
> It would be really valuable to newcomers to have something like this
> availabe easily....
>
> Thanks for you efforts Ian. I'm pretty sure I'm beginning to see it now,
> but its things like the explanation of the event mechanism that are vital.
>
> One of the problems with tutorials is that they assuse that the reader is
a
> new
> programmer, and that the the tricky bits can be explained as magic. I'm
not

> comfortable
> with this. I want to know eactly what is going on.
>
> However your exaplanations look clear, and I think penny isDropping
>
> Thanks loads
>
> Phil
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by plewis66
Phil,

> Maybe a I could suggest some of my other requirements. I may not have
> to write this after all :-)

Sure, suggest away. The next three steps I had in mind were

1) Adding a 'borrowedDate' to the Book class (and possibly an 'author'?) and
then converting the two list views into a single sortable ListView with
three (or four) columns - title/author?/borrower/date.
2) Create a separate Dialog subclass which enables the entry of new books
into the Library, a way of removing books and also a save/load mechanism.
3) Add a way of loaning out (after getting the borrowers name of course <g>)
and returning a book

I'm flexible though.

It may also be that Andy would prefer to take over as I've rather jumped the
gun on his NightSchool (sorry Andy). Maybe he has some other plans and
having two ongoing threads might be a bit confusing?

> Actually, the whole thing started because I have about 150 of my
> personal computing books in my office, and people regularly borrow
> them.

That's good. I was a bit worried about scaling up some of the things I had
in mind but 150 books will be fine. An upgrade to provide a new British
Library cataloguing system might be a bit difficult though <g>

> I wanted to keep track of who has what. I could have used Excel,
> or Access, but that would have been boring!

There are probably worse things you could use, but there are not many more
enjoyable than Smalltalk.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Phil Lewis-2
Hi Ian,

I just got round to actually typing the code for stage 1.

I feellike it is all clear now. I can see exactly what is happening, and it
all makes sense.

However, I could only get it to work by chaning the onLibraryChanged and
onLibraryStatusChanged so we send model: to ListPresenter, rather list:, as
in:

onLibraryChanged
    booksPresenter model: ...
    "Not booksPresenter list: ..."

Also, in LibraryShell onOpenView, we send onLibraryChanged, and
onLibraryStatusChanged, but the former sends the latter. Can we dispense
with onLibraryStatusChanged on onOPenView?

I might sound churlish here, but basically, I think you have a relly good
tutorial building up, and even thought eh text is more important than the
code, maybe these points are worth considerring, if they maybe make the code
a bit more correct?

Tomorrow, I will try to get in the remaining bits.

Thanks for all this

Phil




"Ian Bartholomew" <[hidden email]> wrote in message
news:91vtjt$9li$[hidden email]...
> Phil,
>
> > Maybe a I could suggest some of my other requirements. I may not have
> > to write this after all :-)
>
> Sure, suggest away. The next three steps I had in mind were
>
> 1) Adding a 'borrowedDate' to the Book class (and possibly an 'author'?)
and
> then converting the two list views into a single sortable ListView with
> three (or four) columns - title/author?/borrower/date.
> 2) Create a separate Dialog subclass which enables the entry of new books
> into the Library, a way of removing books and also a save/load mechanism.
> 3) Add a way of loaning out (after getting the borrowers name of course
<g>)
> and returning a book
>
> I'm flexible though.
>
> It may also be that Andy would prefer to take over as I've rather jumped
the

> gun on his NightSchool (sorry Andy). Maybe he has some other plans and
> having two ongoing threads might be a bit confusing?
>
> > Actually, the whole thing started because I have about 150 of my
> > personal computing books in my office, and people regularly borrow
> > them.
>
> That's good. I was a bit worried about scaling up some of the things I had
> in mind but 150 books will be fine. An upgrade to provide a new British
> Library cataloguing system might be a bit difficult though <g>
>
> > I wanted to keep track of who has what. I could have used Excel,
> > or Access, but that would have been boring!
>
> There are probably worse things you could use, but there are not many more
> enjoyable than Smalltalk.
>
> Ian
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
Phil,

> I might sound churlish here, but basically, I think you have a relly good
> tutorial building up, and even thought eh text is more important than the
> code, maybe these points are worth considerring, if they maybe make the
code
> a bit more correct?

You're quite right, and I apologise for the errors. I'll create some
packages containing the code, which should get around any typos introduced
when I copy/paste by providing a references, and also take a bit more care!!

> Also, in LibraryShell onOpenView, we send onLibraryChanged, and
> onLibraryStatusChanged, but the former sends the latter. Can we dispense
> with onLibraryStatusChanged on onOPenView?

Yes, I hadn't noticed the duplication.

> However, I could only get it to work by chaning the onLibraryChanged and
> onLibraryStatusChanged so we send model: to ListPresenter, rather list:,
as
> in:
>
> onLibraryChanged
>     booksPresenter model: ...
>     "Not booksPresenter list: ..."

There was an error, but it's not really the sending of #list instead of
#model. Perhaps going back to basics (triggering the events ourselves) for
the first example was a mistake as it opened up a couple of cans of worms
that don't normally appear.

You said you liked full explanations so ....

Why to use #list instead of #model
-----------------------------------

In the createComponents method we created a MVP
(ListModel/VistBox/ListPresenter) triad for  booksPresenter. As a demo, we
can do the same thing for ourselves in a workspace. Evaluate

x := ListPresenter show.

Inspecting x shows that it's model is, as expected a ListModel. We can now
populate this model using #list

x list: (OrderedCollection with: 1 with: 2 with: 3)

and sure enough the ListBox now displays the list. We can then add another
item to the list.

x model add: 99

What happens, as has been discussed before, is that when we add the item to
the ListModel it triggers an event. The ListBox responds to this event by
fetching the new contents of the list from the ListModel and displaying it
on the screen.  This is the way the ListPresenter MVP is intended to work.

Using a OrderedCollection as the MVP's _model_, as you suggested, will also
work to a certain extent.

x := ListPresenter show.
x model: (OrderedCollection with: 1 with: 2 with: 3)

The list visually updates as expected. At first look it appears that the
ListPresenter doesn't mind what sort of object it has for a model and can
cope with an OrderedCollection (see BasicListAbstract>>refreshContents for
why). However, when you now try

x model add: 99

the list doesn't visually update. Why not? When we added an item to the
"proper" ListModel above it triggered an event that was picked up by the
ListBox. Adding an item to an OrderedCollection does not trigger an event so
the ListBox is never told that it's associated model has changed.

What was the error in the original?
=======================

Too much cut/paste <g>. The following method was wrong in the original post,
we wanted to update the booksPresenters list with the complete library and
not just the available books

onLibraryChanged
    booksPresenter list: self model books.
    self onLibraryStatusChanged

However, when you make this change to correct the code you will see that the
Library app still doesn't work as expected. This is because of the cans of
worms I mentioned above.

Can 1
====

This is partially a general Smalltalk thing. The Library class, when asked
for it's list of books, answers the actual collection that it holds
internally. This is asking for trouble as it gives any object that asks for
the book list the ability to change it, the ability to change an object that
should really only be changed by Library itself.

For immutable objects, an Integer for example, this doesn't matter - there's
nothing they can do to change the Integer itself (they can't change a 3 into
a 4). With a collection though they can. You don't want to give a person who
borrows a book from you the master list of books in the library so that he
can erase the reference to the book (s)he borrowed and never have to return
it.

That was a rather long winded way of getting to the problem this causes us
here. The actual object that is returned by the Library when asked for it's
book collection is always the _same_ physical Smalltalk object - an instance
of OrderedCollection. The contents of this collection may change as books
are added or removed from the Library but the object answered by #books is
_always_ the same OrderedCollection.

The solution to both the above problems is that if you want to prevent a
collection being changed by someone who should only be able to read it you
should answer a _copy of the collection, not the original collection itself.
This also solves the second problem because every copy will then be a
different Smalltalk object. [1]

Library>>books
    ^books copy

Can 2
====

Why does this affect the Library?. When we change the list contained within
a ListModel Dolphin does a little test first (see ListModel>>list:). What
this does is to prevent the visual updating of a list if the list has not
changed _using an Identity test_ rather than an Equality test [2]. The
effect is that #list: does not trigger a #listChanged event if the list
object has not changed irrespective of whether the _contents_ of the list
have changed.

As the original Library returned the same object every time the #listChanged
event was never triggered and the visual list never updated after the
initial display. Answering a copy, as above, solves this as the Identity
test never succeeds and #listChanged is always fired.

Ian

[1]
For completeness I should stress that the copy operation (a shallowCopy)
only copies the collection. The Book objects within the collection are not
copied and any alteration of a book by the receiver of the collection will
affect the original book object. This can be circumvented by a deepCopy -
but that's another story.

x := OrderedCollection with: 1->2 with: 3->4.
y := x copy.
y at: 1 put: 'hello'.
x at: 1  answers 1->2

Unchanged so x and y are separate collections - changing y doesn't affect x

x := OrderedCollection with: 1->2 with: 3->4.
y := x copy.
(y at: 1) key: 99.
x at: 1  answers 99->2

Showing that x and y are still both pointing at the same Association
instance.

[2]
Identity means the same object and uses == and ~~
a := #(1 2 3).
b := #(1 2 3)
a == b  answers false as they are different objects

Equality means that same contents (or rather, however the object itself
wishes it's equality to be measured) and uses = and ~=
a := #(1 2 3).
b := #(1 2 3).
a = b  answers true


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
>
I'll create some
> packages containing the code, which should get around any typos introduced
> when I copy/paste by providing a reference

OK. I've created a web page containing text versions of the original posts
and package files, one per episode/Dolphin version.  I think I've kept
everything straight, if not then please let me know.

http://www.iandb.org.uk/~iandb/specific_mvp_troubles.htm

Ian


Reply | Threaded
Open this post in threaded view
|

Re: The Event System Explained (was: Re: Specific MVP troubles (Long))

Chris Uppal-2
In reply to this post by Peter van Rooijen
Peter van Rooijen wrote:

> 3.
> a) Events are most intuitively seen as objects that represent the fact
that
> a certain type of thing (for lack of a better word) has happened (at a
> certain point in time). An EventType would then represent the type of
> 'thing' that has happened. And an Event could be seen as a sort of
instance
> of an EventType.
>
> b) But it never works that way. It could work that way, but a more
> lightweight (no need for Event and EventType classes) system does the job
as
> well (in Smalltalk). The idea is exactly the same, though.

There's another way of thinking about events which I prefer, and which makes
it seem clearly innapropriate to create EventType classes.

I think of Object>>trigger:[with:] as a normal message send, *except* that
instead of being directed to one known target object, it is *broadcast* to
"whomever it may concern".

Now, since Smalltalk lacks a special syntax for a broadcast message send, it
is necessary to use an analogue of #perform:[with:].

 From this point of view, it's just an accident that you can see the
implementation of the event system of #trigger: in the image (the
doubly-weak lookup tables, etc) but #perform: is a VM primitive.  A
different designer might have chosen to give #trigger: a VM-level, highly
optimised implementation.

(BTW.   It could be argued that the current implementation (not just in
Dolphin) is slow because there's no need for a fast #trigger: when it's
mainly used for user interactions.  But that is circular, since it's the
slowness (compared with a straight message send) and lack of a special
syntax that discourages its use in more general coding contexts.)

> Peter van Rooijen

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Chris Uppal-2
In reply to this post by Ian Bartholomew-3
Ian,

> Out of interest - is anone reading these?

I am too.  Great stuff!  A very nice Christmas present to the Dolphin
community.  Thanks.

(BTW, I am going to avail myself of your invitation to comment on your
approach, but I'll do that in a different branch of the thread)

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Chris Uppal-2
In reply to this post by Ian Bartholomew
Ian,

> I should also mention (as I have this mental picture of other Dolphin
users
> sitting there shaking their heads and muttering "I wouldn't have done it
> like that") that there isn't just the _one way to do most of this stuff.
If
> someone can see a better way of doing something, or for some reason just
> doesn't like the way I did it, then please share it with the rest of us.
I'm
> still learning as well <g>

Thanks for the invitation ;-)

So, and at the risk of muddying the waters, here's what I'd have done
differently and why.  I'd be interested to hear what other MVPers
(experienced or not) think.

(OTOH, if you are an MVP novice and you find the stuff below confusing, then
I *strongly* recommend that you just ignore it -- Ian's approach is sound,
and it would be a shame to spoil his excellent presentation.)

The first thing I'd do differently is that, as a matter of policy, I always
allow the model to change after set-up. That means that there are two
generic changes from the "patterns" Ian has been using.  One is that any
event wiring with the model is set up in the #model: method, e.g:

    myPresenter>>model: aModel
        super mode: aModel.
        aModel when: #statusChanged send: #onStatusChanged: to self.
        "... and then whatever code is needed to refresh the sub-presenters"

rather than, say, in the #createSchematicWiring, or #onViewOpened.  The
second is that you have to remove the triggers from the old model, so the
example above should read:

    myPresenter>>model: aModel
        super model isNil ifFalse:
            [super model removeEventsTriggeredFor: self].
        super mode: aModel.
        aModel when: #statusChanged send: #onStatusChanged: to self.
        "... and then whatever code is needed to refresh the sub-presenters"

(Which is a little bit of extra effort, but it's boilerplate stuff).

Why would I want to do that ?  Well, suppose that you change the application
to manage a library *system*.  So there'd be a Collection of Libraries, and
the main application might have a ListPresenter showing the libraries, a
sub-presenter showing the details for the currently selected Library.  In
this case the subpresenter would be just like the Presenter that Ian had
been building except that it's not a top-level "shell".

I find it easier to get into the habit of writing all of my MVP triads as if
they might end up as sub-components later on, rather than have to think
about whether that might ever happen or not.   It is, after all, only 2
extra lines of code, and it encourages a nice split of responsibility
between #createComponents, #createSchematicWiring, and #model:.  (As a
side-effect it also means that I almost never have to have a #onViewOpened,
which pleases me because I think it's a bit hacky.)

The second difference in my approach is to do with the ListModels.

There's an asymmetry between Library>>books and Library>>availableBooks
which disturbs me a little.  Why should the Presenter have to know that one
is a ListModel and the other a Collection ?   My inclination would be to
make all the methods on Library answer derived collections of Books.  So
that I'd have:

Library>>books
    ^ books copy.    "safer and more consistent
                            to make a shallow copy here,
                            rather than just answer the
                            internal collection"

Library>>availableBooks
    ^ books select: [:each | each isAvailable].

That means that the event interface between the model and the presenter
requires more code (as in one of Ian's earlier versions) since the shared
ListModel isn't looking after that for you.  But, on the other hand, it does
make the event interface explicit, and it also allows you to craft the
interface to the specific needs of the problem domain (the ListModel event
set is a little crufty, in my opinion).

I have several times attempted to use ListModel as a sub-object of my main
model, and it has always caused me problems.  For instance, a ListModel
sorts itself by telling the underlying collection to sort itself, which
means that the Presenter is actually changing the Model's data -- not good,
I think.  Also if you have two ListPresenters on the same model (using
enhanced list views), then clicking on one view's column header will cause
both views to be sorted.

So, these days, I have given up on using ListModels explicitly -- I don't
think they are very much use except for internal use within a ListPresenter
triad.  Thus my LibraryShell would handle the Libraries #books and
#availableBooks in the same way:

LibraryShell>>onLibraryStatusChanged
    availableBooksPresenter list: self model availableBooks

LibraryShell>>onLibraryChanged
    booksPresenter list: self model books.

(Actually, I probably wouldn't provide the Library>>availableBooks method,
it's quite simple for users of the model to create their own filtered
sub-lists, so I'd probably leave it to them.)

One last difference.  I'm inclined to provide as much data as is
conveniently possible at the time when an event is triggered.  The argument
is that it costs nothing to provided it, and the Presenter can always ignore
any data it doesn't need.  (In fact the typical pattern of my MVP stuff is
that the Model generates all sorts of detail, but then the Presenter just
traps all the events but ignores the details and just refreshes itself
completely from the model.)  So, I think I'd have the Library generating
events something like:

Library>>addBook: aBook
    books add: aBook.
    self trigger: #bookAdded: with: aBook.

Library>>removeBook: aBook
    books remove: aBook ifAbsent: [^ self].
    self trigger: #bookRemoved: with: aBook.

Library>>checkOut: aString to: aPerson
    | book |
    book := self getBook: aString.
    book onLoanTo: aPerson.
    self trigger: #bookStatusChanged: with: book.

Library>>return: aString
    | book |
    book := self getBook: aString.
    book beAvailable.
    self trigger: #bookStatusChanged: with: book.

(BTW, it could be argued that Books should be generating the
#bookStatusChanged events themselves, and that Library should be listening
for these and forwarding them on to its listeners.  In a more complex
application that might be a better design choice, but it is certainly not
worthwhile in this simple system.)

And then, in theory, my Presenter event handling would look like:

LibraryShell>>onBookAdded: aBook
    booksPresenter list: self model books.
    availableBooksPresenter list: self model availableBooks.

LibraryShell>>onBookRemoved: aBook
    booksPresenter list: self model books.
    availableBooksPresenter list: self model availableBooks.

LibraryShell>>onBookStatusChanged
    availableBooksPresenter list: self model availableBooks.

Which should be refactored to remove the repetition.  However, in practise
I'd be much more likely to have a single update method, so it would read:

LibraryShell>>model: aLibrary
    super model isNil ifFalse:
        [super model removeEventsTriggeredFor: self].
    super model: aLibrary.
    aLibrary
        when: #bookAdded: send: #onModelChanged to self;
        when: #bookRemoved: send: #onModelChanged to self;
        when: #bookStatusChanged: send: #onModelChanged to self.
    self onModelChanged.

LibraryShell>>onModelChanged
    booksPresenter list: self model books.
    availableBooksPresenter list: self model availableBooks.

That's all for now.  Once again, I hope I haven't confused anybody.  If I
*have* confused you then please just ignore everything I've written.

    -- chris

P.S.  Happy Christmas!


Reply | Threaded
Open this post in threaded view
|

Re: The Event System Explained (was: Re: Specific MVP troubles (Long))

Peter van Rooijen
In reply to this post by Chris Uppal-2
"Chris Uppal" <[hidden email]> wrote in
message news:[hidden email]...

> Peter van Rooijen wrote:
>
> > 3.
> > a) Events are most intuitively seen as objects that represent the fact
> that
> > a certain type of thing (for lack of a better word) has happened (at a
> > certain point in time). An EventType would then represent the type of
> > 'thing' that has happened. And an Event could be seen as a sort of
> instance
> > of an EventType.
> >
> > b) But it never works that way. It could work that way, but a more
> > lightweight (no need for Event and EventType classes) system does the
job
> as
> > well (in Smalltalk). The idea is exactly the same, though.
>
> There's another way of thinking about events which I prefer, and which
makes
> it seem clearly innapropriate to create EventType classes.

Looks to me it is an additional point of view, not a competing one. And it's
even more lightweight, in terms of fewer objects and messages.

> I think of Object>>trigger:[with:] as a normal message send, *except* that
> instead of being directed to one known target object, it is *broadcast* to
> "whomever it may concern".

That is of course assuming that there is a way to know whom it may concern
:-).

> Now, since Smalltalk lacks a special syntax for a broadcast message send,
it
> is necessary to use an analogue of #perform:[with:].

Indeed.

> From this point of view, it's just an accident that you can see the
> implementation of the event system of #trigger: in the image (the
> doubly-weak lookup tables, etc) but #perform: is a VM primitive.  A
> different designer might have chosen to give #trigger: a VM-level, highly
> optimised implementation.

Absolutely. Perhaps an ideal Smalltalk would have exactly that. Or rather,
both mechanisms would be highly optimized, where #perform: is logically a
special case of #broadcast:, but because it happens so often, presumably has
its own primitive implementation.

And the reason, as far as I'm aware that it doesn't work that way, is that
the event system was tacked on later (much later, probably), as a
generalization of the dependency mechanism for the Smalltalk-80 GUI. I would
say that it's great that that was possible.

Your train of thought can also lead us to see the difference between
performing and broadcasting as an analog of the difference between tell (to
a specific addressee) and say (for the benefit of whomever is listening).
Then it's easy to recognize how tell is a special case of say, as well as
the potential for performance benefits.

> (BTW.   It could be argued that the current implementation (not just in
> Dolphin) is slow because there's no need for a fast #trigger: when it's
> mainly used for user interactions.  But that is circular, since it's the
> slowness (compared with a straight message send) and lack of a special
> syntax that discourages its use in more general coding contexts.)

I agree with you there, too :-). I have a good friend who seemed to do
everything he could think of with events, though (in VSE). His apps weren't
slow, however. But there has always been a big downside of event code (to
me, anyway), and it's debugging. But perhaps that observation is begging the
question also (could we have tool support so good that event connections are
no-brainers to set-up, trace and debug)?

GO NATIVE EVENTS.

Regards,

Peter van Rooijen


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew
In reply to this post by Chris Uppal-2
Chris,

> Thanks for the invitation ;-)

You're welcome <g>. This is one thing that I think Usenet is really useful
for, the ability to say something and get long well thought out and reasoned
replies that make you consider and, hopefully, better understand the overall
picture. Smacks a bit of the long letters that the Victorian scientists used
to write to each other.  On the other hand I suppose the delights of flame
wars and interminable "mine is better than yours" threads was not something
they had to worry about.

> The first thing I'd do differently is that, as a matter of policy, I
always
> allow the model to change after set-up.
....
> I find it easier to get into the habit of writing all of my MVP triads as
if
> they might end up as sub-components later on, rather than have to think
> about whether that might ever happen or not.   It is, after all, only 2
> extra lines of code, and it encourages a nice split of responsibility
> between #createComponents, #createSchematicWiring, and #model:.

I can see your reasoning here but it's (converting Shells into sub
components)  not something I tend to do all that often. Perhaps I should
think more about it - creating reusable MVP triads is what it is all about
after all.

>       (As a
> side-effect it also means that I almost never have to have a
#onViewOpened,
> which pleases me because I think it's a bit hacky.)

I do tend to overuse it but I think it goes back to the early days of
Dolphin where (a hazy mist has appeared) the view was not available as early
as it is now, you couldn't access it from #createSchematicWiring or #model:.
Because of this #onViewOpened became a useful place to do all the little
initialisation jobs that involved the view - and using it has stuck even
thought it's not necessary.

On the other hand, consistently using #createComponents,
#createSchematicWiring and #model: for the initialisation of the MVP triad
and using #onViewOpened for any initialisation involving objects outside the
triad might not be a bad thing.  Have to think about that a bit.

> The second difference in my approach is to do with the ListModels.

I think this is more of a scale thing. Using a ListModel in simple MVP
triads tends to make them even simpler. The next step in the Library example
converts the two views into a ListView with three columns. Using the shared
ListModel means that you can remove nearly everything from the Shell that is
not involved in initialisation and just let the ListModel/ListPresenter get
on with it.

However, once you get past the simple stage then it may well be that
ListModel simplifies things too much and you will be better off maintaining
the collections, and collection events, yourself. The ListPresenter can just
be the point where you gain access to it's private ListModel.

> I have several times attempted to use ListModel as a sub-object of my main
> model, and it has always caused me problems.  For instance, a ListModel
> sorts itself by telling the underlying collection to sort itself, which
> means that the Presenter is actually changing the Model's data -- not
good,
> I think.  Also if you have two ListPresenters on the same model (using
> enhanced list views), then clicking on one view's column header will cause
> both views to be sorted.

When I use a ListModel I very rarely wrap it around a SortedCollection but
usually use an OrderedCollection. This ties in with the fact that I usually
use then with EnhancedListViews and would probably provide SortBlocks for
each column, making the underlying SortedCollection sortblock redundant.

> One last difference.  I'm inclined to provide as much data as is
> conveniently possible at the time when an event is triggered.

Yes, Completely agree with you here. Having the trigger able to include the
reason why it has been triggered is an extremely useful facility.  It can
also be used to allow that much more encapsulation and therefore protection.
If an object doesn't have to answer a request to provide information about
one of it's internals - an event was triggered and the receiver is enquiring
about what has changed - but can let the receiver know just as much as it
needs to as part of the trigger then that can reduce an objects interface.

> (BTW, it could be argued that Books should be generating the
> #bookStatusChanged events themselves, and that Library should be listening
> for these and forwarding them on to its listeners.  In a more complex
> application that might be a better design choice, but it is certainly not
> worthwhile in this simple system.)

Apart from the more normal ways of doing this you can also use Dolphins
ability to trigger events off of classes as opposed to instances of classes.
A Book instance triggers a class event, #aBookChanged, and any
Library instance that has registered for this Book class event can refresh
it's lists.

Library>>createSchematicWiring
    Book when: #aBookHasChanged send: #onLibraryStatusChanged to: self

Book>>beAvailable
    self class trigger: #aBookHasChanged

Has a somewhat clunky feel to it though <g>

> That's all for now.  Once again, I hope I haven't confused anybody.  If I
> *have* confused you then please just ignore everything I've written.

Or better still, don't ignore it but put it aside and come back to it at a
later date. Chris makes some very good points above.

> P.S.  Happy Christmas!

Yep, Happy Christmas to all the Dolphineers (or Delphiniums as my
spellchequer just suggested) around the world.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ted Bracht-2
In reply to this post by Ian Bartholomew-3
Hi Ian,


> > Actually, the whole thing started because I have about 150 of my
> > personal computing books in my office, and people regularly borrow
> > them.
>
> That's good. I was a bit worried about scaling up some of the things I had
> in mind but 150 books will be fine. An upgrade to provide a new British
> Library cataloguing system might be a bit difficult though <g>
>

Now there's a thing. What if you have two (or more) identical books. You
don't want the entry to be repeated x times in the list. Maybe add a couple
of quantities to it?

Ted


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew
Ted,

>                                What if you have two (or more) identical
books. You
> don't want the entry to be repeated x times in the list.

Using Phil's original design, which is what I have been trying to follow,
you do need each instance of multiple books to appear in the list. The
person borrowing the book is stored in the Book itself  (in the borrower
instVar). If you want to easily see who has borrowed a book and when, as the
next version does, then you need to be able to see all the books.

I'm not 100% convinced of the advisability of making the Book aware of its
borrower, it doesn't seem natural in some ways. On the other hand it works
quite well ....

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Reinout Heeck
Ian Bartholomew wrote:
>
>
> I'm not 100% convinced of the advisability of making the Book aware of its
> borrower, it doesn't seem natural in some ways. On the other hand it works
> quite well ....
>

(Just jumping in in mid thread)

How about renaming borrower to location, does it feel more natural then?

You can now browse borrowers with the same tools as the local library.


R
-


Reply | Threaded
Open this post in threaded view
|

Re: The Event System Explained (was: Re: Specific MVP troubles (Long))

Chris Uppal-2
In reply to this post by Peter van Rooijen
Hmm, it's very late to reply, but...

[Also, I'd have moved this to C.L.S, but too much of the background is in
this newsgroup for it to make much sense out of context]

Peter van Rooijen wrote:
> > There's another way of thinking about events which I prefer, and which
> makes
> > it seem clearly innapropriate to create EventType classes.
>
> Looks to me it is an additional point of view, not a competing one. And
it's
> even more lightweight, in terms of fewer objects and messages.

Oh, I agree.

> > I think of Object>>trigger:[with:] as a normal message send, *except*
that
> > instead of being directed to one known target object, it is *broadcast*
to
> > "whomever it may concern".
>
> That is of course assuming that there is a way to know whom it may concern
> :-).

In this view the object against which the event is triggered is only acting
as a "channel ID" (in the sense of, say, a TV channel).  It would be
perfectly possible, and sometimes it is appropriate (though not often), to
trigger events off some other object.  In fact I can't see any good reason
why an application design might not have objects whose *only* purpose was to
act as channel identifiers in this way.  (I shall have to remember this
point, I suspect it could make some designs quite a bit cleaner.)

> Your train of thought can also lead us to see the difference between
> performing and broadcasting as an analog of the difference between tell
(to
> a specific addressee) and say (for the benefit of whomever is listening).
> Then it's easy to recognize how tell is a special case of say, as well as
> the potential for performance benefits.

That's a good way of putting it.

> > (BTW.   It could be argued that the current implementation (not just in
> > Dolphin) is slow because there's no need for a fast #trigger: when it's
> > mainly used for user interactions.  But that is circular, since it's the
> > slowness (compared with a straight message send) and lack of a special
> > syntax that discourages its use in more general coding contexts.)
>
> I agree with you there, too :-). I have a good friend who seemed to do
> everything he could think of with events, though (in VSE). His apps
weren't
> slow, however. But there has always been a big downside of event code (to
> me, anyway), and it's debugging. But perhaps that observation is begging
the
> question also (could we have tool support so good that event connections
are
> no-brainers to set-up, trace and debug)?

Interesting to think about that.  What would be required to make it work ?

Let's start by committing ourselves to the view I've been talking about.
We'll drop the term "event" and simply think in terms of broadcasting a
message.  Hence, we have methods on Object
    #broadcast: <aSelector>
    #broadcast: <aSelector> with: <anObject>
    ...
    #broadcast: <aSelector> withAll: <anArray>

Secondly, in the current scheme the observer chooses what message will be
sent to it when an event is triggered; also it can choose to have the
message sent to some other object, and it can even change the arguments to
the message send.  From the point of view that I'm taking, this all looks
like unnecessary and over-engineered fluff.  It provides a level of
flexibility which is rarely needed, but which is easy to provide *given the
current implementation*.  A "design burp" in fact.

So, lets assume that there is just one basic way an object can register
itself as a listener to another object's broadcasts:
    Object>>receive: <aSelector> from: <anObject>

plus the obvious convenience method:
    Object>>receiveAll: <anArrayOfSelectors> from: <anObject>

and the corresponding unregistering methods:
    Object>>dontReceive: <aSelector> from: <anObject>
    Object>>dontReceiveFrom: <anObject>
    Object>>dontReceiveAll: <anArrayOfSelectors> from: <anObject>

(Better names might be found, but these'll do to be going on with.)

Now, we have a useful simplification.  In fact I think the above would be
easier to use than the current scheme.

However, we've also increased the coupling between observer and observee, in
that there is now an increased chance of conflicts between the names of the
methods that the observee broadcasts, and the method names used by its
observers.  That is an inherent downside, I'm afraid, but the effects can be
minimised by sensible choice of method names.

E.g. in the library example, the code for adding a book to a library might
look like:

Library>>addBook: aBook
    books add: aBook.
    self
        broadcast: #notifyBookAdded:toLibrary:
        with: aBook
        with: self.

BTW, the code where the LibraryShell arranges to observe the Library might
look like:

LibraryShell>>model: aLibrary
    | broadcasts |
    broadcasts := #(#notifyBookAdded:toLibrary:
                            #notifyBookRemoved:toLibrary:
                            #notifyBookStatusChanged:toLibrary:).

    self dontReceiveAll: broadcasts from: self model.
    super model: aLibrary.
    self receiveAll: broadcasts from: self model.

    self onModelChanged.

(which would still benefit from some refactoring, but notice that the ugly
#notNil test has vanished from my original formulation of this method.  Also
note that I'm still assuming that LibraryShell will simply forward all
notifications to a general-purpose #onModelChanged method -- this
formulation would force me to add forwarding methods
#notifyBookAdded:toLibrary:, etc, to LibraryShell which are not required
under the current scheme.  I don't mind that.)

Now -- at last -- we've reached the point where properly useful tool support
becomes easy.

First off, notice that the ordinary "definitions" and "references" browsers
work correctly with these new broadcast messages without any changes to the
system at all.  I think that's a good start in itself.

Secondly, assume that #broadcast: and its friends are implemented directly
be the VM -- just like #perform:.  In this case debugging into a broadcast
message would be essentially the same as debugging into a normal message
send, you'd just step (almost) directly into the observer's code.  If the
broadcast stuff isn't implemented by the VM (e.g. if it were implemented on
top of the current system in Dolphin) then it'd be useful to enhance the
debugger to recognise the specific #broadcast: messages and skip over their
implementations and straight to the observer's code.  There are already
similar facilities in the Dolphin debugger (e.g. to skip over critical
sections), so that might not even be very hard to implement.  (BTW, if Andy
or Blair are reading, a similar feature for Dolphin's actual event system
would be really nice.  Is it feasible?)

I can't think of anything else that needs to be added to give broadcast
messages the same level of support as direct messages, so I claim to have
completed the assignment. ;-)  Sorry this's been such a long message; I hope
*somebody* found it interesting...

> Peter van Rooijen

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: The Event System Explained (was: Re: Specific MVP troubles (Long))

Peter van Rooijen
Hi Chris,

[snip]

> I can't think of anything else that needs to be added to give broadcast
> messages the same level of support as direct messages, so I claim to have
> completed the assignment. ;-)  Sorry this's been such a long message; I
hope
> *somebody* found it interesting...

Very. I'm printing it and hope to reply at a later time. I've found myself
implementing a portable event system today for the benefit of enhancing
SUnit portability by making the dialect-specific components of the user
interface lighter-weight. Interestingly, all that really seems needed is
selective observation (which you seem to have described a design for). A
generalized event system is overkill, and a dependency mechanism (that
classic SUnit has portably) it too simple.

Stories I want to support include separation of the dialect-dependent GUI
code, and ease of slapping a (replacement or additional) user interface on
the portable part, without needing a lot of infrastructure and without
needing any documentation separate from the code, so that the porter's and
the extender's jobs are made as simple as possible, to allow easy
proliferation across dialects (one day to port the whole thing, ready for
redistribution, is inside my goal).

Regards,

Peter van Rooijen


Reply | Threaded
Open this post in threaded view
|

Re: The Event System Explained (was: Re: Specific MVP troubles (Long))

Peter van Rooijen
In reply to this post by Chris Uppal-2
[snip]

> Interesting to think about that.  What would be required to make it work ?
>
> Let's start by committing ourselves to the view I've been talking about.
> We'll drop the term "event" and simply think in terms of broadcasting a
> message.  Hence, we have methods on Object
>     #broadcast: <aSelector>
>     #broadcast: <aSelector> with: <anObject>
>     ...
>     #broadcast: <aSelector> withAll: <anArray>
>
> Secondly, in the current scheme the observer chooses what message will be
> sent to it when an event is triggered; also it can choose to have the
> message sent to some other object, and it can even change the arguments to
> the message send.  From the point of view that I'm taking, this all looks
> like unnecessary and over-engineered fluff.  It provides a level of
> flexibility which is rarely needed, but which is easy to provide *given
the
> current implementation*.  A "design burp" in fact.

More than we often need, anyway.

> So, lets assume that there is just one basic way an object can register
> itself as a listener to another object's broadcasts:
> (Better names might be found, but these'll do to be going on with.)

[snipped design]

> Now, we have a useful simplification.  In fact I think the above would be
> easier to use than the current scheme.
>
> However, we've also increased the coupling between observer and observee,
in
> that there is now an increased chance of conflicts between the names of
the
> methods that the observee broadcasts, and the method names used by its
> observers.  That is an inherent downside, I'm afraid, but the effects can
be
> minimised by sensible choice of method names.

I don't think there's any issue there that can't be easily dealt with by
using an adapter for protocol conversion. I love it when I get more patterns
in :-).

[snip]

> Now -- at last -- we've reached the point where properly useful tool
support
> becomes easy.

And that's an important achievement. For the SUnit enhancement project, I've
decided to go with a combination/variation of what we had and your proposed
mechanism, but a little simpler even, and I personally like the
communicative properties of the resulting code a little better, too.

Here is some of the code. I'll comment it.

The mechanism does not require *any* code in object. Offering observability
by broadcasting messages is the metaphor. Since all objects can already
receive messages, there's no need to extend any base class. Big plus for me,
this.

You can make a broadcaster easily by subclassing off SUnitBroadcaster. Then
you tell your broadcaster that it will be monitored by you (or by an adapter
for you, or what have you), for a set of broadcasts (which are Symbols, and
the observer will need to process messages with those Symbols as selector).

A broadcast message always passes one argument, the broadcasting object.
This keeps everything simple, and it's all that's really needed.

Have a look:

!SUnitBroadcaster publicMethods !

broadcast: selector
|
monitors
|
monitors := self monitoredBroadcasts
 at: selector
 ifAbsent: [^self]
.
monitors do:
 [:m | m perform: selector with: self]
  !

monitoredBroadcasts

^
monitoredBroadcasts
 ifNil: [monitoredBroadcasts := IdentityDictionary new]!

monitoredBy: anObject forBroadcasts: broadcastSelectors

broadcastSelectors isEmpty
 ifTrue: [
  self monitoredBroadcasts
   removeKey: anObject
   ifAbsent: []
 ]
 ifFalse: [
  broadcastSelectors
   do: [:selector | | monitors |
    monitors :=
     self monitoredBroadcasts
      at: selector
      ifAbsentPut: [Set new]
     .
     monitors add: anObject
   ]
 ]! !

So, to be a broadcaster you need only those 3 methods. Note that this is a
*complete implementation*, not just some excerpts of the code.

Setting up observation/monitoring of an object looks like this (this is in
the new SUnit Control Panel that uses the existing TestRunner as an engine -
only for part of the functionality how, so the list of broadcasts will
grow):

setEngine
 engine := SUnitTestRunner current.
 engine dumpMonitors. "for convenience during development"
 engine
  monitoredBy: self
  forBroadcasts: #(
   #changedTestCaseClassesIn:
   #changedTestBatchScriptIn:
   #changedBrokenTestCasesIn:
   #changedActiveBrokenTestCaseIn:
   #changedCommandRunAvailabilityIn:
   #changedCommandDebugAvailabilityIn:
   #changedCommandBrowseAvailabilityIn:
 )
 .
 engine openInShellView! !

And this is how the TestRunner broadcasts messages (the broadcasts are
simply added to the already working code):

doCommandRefreshTestCaseClasses
 self updateTestCaseClassesList.
 self broadcast: #changedTestCaseClassesIn:!

Finally, this is how the broadcast message is processed by by the SUnit
Control Panel:

changedTestCaseClassesIn: e

listTestCaseClasses items:
 e testCaseClasses!

That's it! It's so simple.

> First off, notice that the ordinary "definitions" and "references"
browsers
> work correctly with these new broadcast messages without any changes to
the
> system at all.  I think that's a good start in itself.

Only one problem that I see, and it's that my IBM VisualAge/VA Assist image
doesn't find the method with a broadcast Symbol in a literal array as a
sender of that Symbol. Wonder if that's a setting, and what Dolphin does.

> Secondly, assume that #broadcast: and its friends are implemented directly
> be the VM -- just like #perform:.  In this case debugging into a broadcast
> message would be essentially the same as debugging into a normal message
> send, you'd just step (almost) directly into the observer's code.  If the
> broadcast stuff isn't implemented by the VM (e.g. if it were implemented
on
> top of the current system in Dolphin) then it'd be useful to enhance the
> debugger to recognise the specific #broadcast: messages and skip over
their
> implementations and straight to the observer's code.  There are already
> similar facilities in the Dolphin debugger (e.g. to skip over critical
> sections), so that might not even be very hard to implement.  (BTW, if
Andy
> or Blair are reading, a similar feature for Dolphin's actual event system
> would be really nice.  Is it feasible?)

Even without selector recognition or his support, it's already quite
transparent.

> I can't think of anything else that needs to be added to give broadcast
> messages the same level of support as direct messages, so I claim to have
> completed the assignment. ;-)

Well, you're right, it works :-).

Regards,

Peter van Roooijen
Amsterdam


123