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. |
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 |
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 |
> 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/ |
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 > 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 > > |
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 |
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 > > > > |
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 |
>
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 |
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 |
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 |
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! |
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 > 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 |
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 |
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 |
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 |
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 - |
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 |
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 |
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 > 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 |
Free forum by Nabble | Edit this page |