Library machine, still alive

76 views
Skip to first unread message

Andreas Söderlund

unread,
Mar 5, 2017, 1:03:18 PM3/5/17
to object-composition
Hi list, long time to see. I've been in life maintenance mode for a while, but lately I've worked on an interactive remake of the Library borrowing machine in Haxe, for the browser.

It's shaping up quite well after a week of just getting everything together in a nice MVC way. But now there's something I'd really like some feedback on.

According to the use case, "Scanner asks screen to display a list of scanned items." This list of scanned items is causing me some head-scratching.

If I model it as a Role, it differs quite a bit from the other Roles (scanner, screen, card reader and other "physical" objects). The other Roles have a quite natural flow between them. They ask the others to solve a part of the problem, and everything's fine. But the list is basically just temporary state, so the flow between roles turns into something where it doesn't communicate much:

// RoleMethod in scanner
function rfidScanned(rfid : String) {
    var item = library.items().find(function(item) return item.rfid == rfid);

    // Testing scannedItems without giving it any real responsibiliites.
    if(item != null && !scannedItems.isAlreadyScanned(item))
        scannedItems.scanned(item);

    self.waitForItem();
}

So my concern is that I'm just treating scannedItems as a list, hidden behind some RoleMethods. I don't see a reason for any other object than a list to play this role, especially since the object playing scannedItems is created in the constructor. Here's the role currently:

role scannedItems : {
    // Contract
    function iterator() : Iterator<LoanItem>;
    function push(item : LoanItem) : Void;
    function splice(pos : Int, len : Int) : Iterable<LoanItem>;
    
    // RoleMethods
    public function isAlreadyScanned(rfid : String)
        return self.exists(function(item) return item.rfid == rfid);

    public function addItem(item : LoanItem)
        self.push(item);

    public function clear()
        self.splice(0, -1);
}

This is not much more than forwarding of fields with different naming. I must say I enjoy reading, and writing

if(scannedItems.isAlreadyScanned(rfid))

Instead of

if(scannedItems.exists(function(item) { return item.rfid == rfid; }))

But is it useful enough? This takes me to the other option, just make it a field on the Context itself. But it is mentioned in the use case, so I don't feel perfectly happy with that either.

My other issue is about the control flow in the distributed algorithm. Where to put the actual work? As said, for most of the use case there is a natural flow, where the program moves into a RoleMethod that does some work, then passes it to another Role, etc., according to the use case. But "scannedItems" sticks out, since there is no need for it to be a part of the flow, it's just a temporary state. And I could easily move logic from the "screen" Role:

role cardReader {
public function validatePin(card : Card, pin : String) {
   if(card.pin == pin)
       screen.displayScannedItems();
   else
    //...
}
}

role screen {
public function displayScannedItems() {
   self.state = DisplayBorrowedItems(scannedItems);
   // Use case keeps going here
   scanner.waitForItem();
}
}

Instead giving the cardReader more responsibilty like this:

role cardReader {
public function validatePin(card : Card, pin : String) {
   if(card.pin == pin) {
       screen.displayScannedItems();
       // Use case keeps going here now
       scanner.waitForItem();
   }
   else
    //...
}
}

role screen {
public function displayScannedItems() {
   self.state = DisplayBorrowedItems(scannedItems);
}
}

Which could turn the screen into a "state-only" Role like scannedItems, eventually. So what's your idea about the control flow, branching and responsibilities for and between the Roles?

/Andreas

Hai Quang Kim

unread,
Mar 5, 2017, 9:02:33 PM3/5/17
to object-composition
Hi Andreas,

Long time no see :)
Your code example as usual is always very lively and complete with user case, RRR....

Some of my comments: 

"This is not much more than forwarding of fields with different naming."
I am seeing myself doing this a lot too, and I love doing this. It makes the context code much more readable.
It separates the level of abstractions for me (use case messaging level, and data manipulation level...)

"// RoleMethod in scanner
function rfidScanned(rfid : String) {"

Is this a Role method, it ends with _ed. So it does not look like a verb to me :)
Is this for this line: Scanner reads the Loan item

I would just go with the use case here: (note I use '_' for role methods and '.' for methods)

scanner_onNewLoanItem() {  // triggered on the even an new load item is placed on the machine

   var rfid = self.getItemRFID(); // the object should know how to red rfid

   scannedItems_add(rfid);  // I am done, passing the control here

}

scannedItems_add(rfid) {

   if ( library_hasItem( rfid ) )  {

      var item = library_getItem( rfid );

      if ( self.hasItem( item ) ) {

          screen_displayError( "item already scanned");

      } else {

          self.addItem( item );

          screen_updateScannedItems();

      }
   }
   else {

        screen_displayInvalidItem(rfid);

   }

}

I think the scanned Items is not just a temp state, it is visible for the user (can see the list on the screen, think about in the user head....)
It is pretty much a must have Role to me. And in my code the scanned items want to talk to the library to ask for item it want to add, it talks to the screen to show its content....

/quang

Hai Quang Kim

unread,
Mar 5, 2017, 9:30:15 PM3/5/17
to object-composition
One the second part, to me there are two things that one Role need to communicate with other Roles:
1) ask for info to take action
    pattern: 
         - info = otherRole.doYouHaveInfo()
         - self.doMyPartWithInfo( info )

2) passing control
         - otherRole.NextStep()

It is tempting to pack more things in the role methods, but I think I will try to minimize the code in role method and spread the responsibility to other roles if it makes sense (more interaction). This could lead to more interaction and less centralized role method....But I think this is the hard part of how to slice an elephant logically :)

In your example, I would ask:
1) what need to be done: validate the pin
2) who needs to do that: the input pad or the card reader
If we go for input pad then it looks like:

inputPad_onPinEntered() {
   var pin  = cardReader_GetPin();  // get info

   if (self.getEnteredPin() == pin) }   // do work
       screen_displayScannedItems()   // passing control
   } else {
       screen_displayInvalidPin();
   }
}

or we can go with cardReader validate pin....

One thing I notice here: you pass a lot of things to role methods through parameters...which may end up with less interaction.
I would go with minimize the passing param and use role interaction instead.

Eg. Instead of doing this

role cardReader {
public function validatePin(card : Card, pin : String) {
    if(card.pin == pin)
        screen.displayScannedItems();
    else
     //...
}
}

no param, role interaction instead since they "know" each other :)

role cardReader {
public function validatePin() {
    if(card_getPin() == padInput_getEnteredPin())
        screen.displayScannedItems();
    else
     //...
}
}

but in the above code, there is nothing to do with card Reader, so I would try something else:

role cardReader {
public function validatePin() {
            var enteredPin = padInput_getEnteredPin();  // ask for info
    if(self.getReadPin() == enteredPin)   // do work
        screen.displayScannedItems(); // passing control...
    else
     //...
}
}

This is my thinking after trying DCI for a while, I am not sure I am heading to correct direction or not.
But it is good to see how other doing DCI too. Indeed I learnt a lot of things from your code example :)

/quang

On Monday, March 6, 2017 at 2:03:18 AM UTC+8, Andreas Söderlund wrote:

Matthew Browne

unread,
Mar 5, 2017, 11:17:34 PM3/5/17
to object-co...@googlegroups.com

Personally I think adding a new role just for the input pad would be overkill, but I think Quang's suggested questions to ask are an excellent way of thinking about it.

I have mixed feelings about the notion that roles need to have significant behavior of their own, i.e. that it's not a role if it doesn't have much (or any) difference in behavior from what its role player already provides via instance methods. One of the great things about a Context is that it allows you to see how objects are interacting with each other, and which objects are participating in each interaction. Some of those objects might not have much behavior unique to the role they are playing but are still participating in the interaction, and it would be nice to highlight that somehow even if we agree that we want to discourage roles without role methods.

I do agree that one could get into a bad habit of just using roles as names for existing data interfaces, i.e.:

role myRole {} requires {...}

(which as you know, the trygve language doesn't even allow anymore without explicitly saying which instance methods you want to expose as role methods.)

But I certainly don't see anything problematic with having a scannedItems role in your example, and I strongly agree with what Quang said. IMO, even just wanting to name a method differently to better describe how it's fulfilling its role in an interaction is a perfectly valid reason to create a role method. I suppose one could go overboard with this, but most of the time better naming results in more readable code.

Cheers,
Matt

--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

Matthew Browne

unread,
Mar 5, 2017, 11:23:51 PM3/5/17
to object-co...@googlegroups.com
Quick question for Quang:

On 3/5/17 9:02 PM, Hai Quang Kim wrote:
> (note I use '_' for role methods and '.' for methods)
Were you playing around with this example in C# and had to use manual
source transformation for that reason, or is there some other reason? I
find this syntax weakens the illusion that DCI is meant to provide (role
methods being added to objects).

Quang

unread,
Mar 5, 2017, 11:49:30 PM3/5/17
to object-composition
I am doing DCI by naming convention. (poor man version)

Role method:  Role_Method(), and data object method: dataObject.method()
I find it is easy to communicate here that way to know which one is role method and which one is data object method.
Maybe self.dataObjectMethod is enough to differentiate from Role.Rolemethod though.

Anyway it is hard to look at code in the thread, I just want to make it clearer.

/quang

Matthew Browne

unread,
Mar 6, 2017, 1:31:47 AM3/6/17
to object-co...@googlegroups.com
On 3/5/17 11:17 PM, Matthew Browne wrote:
> Personally I think adding a new role just for the input pad would be
> overkill, but I think Quang's suggested questions to ask are an
> excellent way of thinking about it.
I may have been too hasty on that; I think I'd have to see the full code
for your current version of the cardReader role. It also depends on
whether or not you consider the keypad to be conceptually a part of the
card reader, which isn't really a programming question.

Trygve Reenskaug

unread,
Mar 6, 2017, 3:35:56 PM3/6/17
to object-co...@googlegroups.com
Kudos to you, Andreas, for waking up the question of a DCI metamodel.
According to the use case, "Scanner asks screen to display a list of scanned items." This list of scanned items is causing me some head-scratching.

If I model it as a Role, it differs quite a bit from the other Roles (scanner, screen, card reader and other "physical" objects). The other Roles have a quite natural flow between them. They ask the others to solve a part of the problem, and everything's fine. But the list is basically just temporary state, so the flow between roles turns into something where it doesn't communicate much:...
My mental model is different: A role does not model anything by itself. So it would be more precise to say that a ScannerRole identifies/names/maps to  an object that represents the physical scanner. This object may play different roles in different contexts. The roles may be named differently in different contects such as BorrowedItems, ReturnedItems, or whatever.

Without having studied the details of your complete solution, I make a guess at the whole. My guess is  that the problem lies in your Data (information) model. This model is distinct from the role models found in the different Contexts. I suspect that the Data model should include the list of scanned items as an object.  The Data are just as important as the Contexts:
Data. What are the objects. Some clearly represent hardware: scanner, screen, card reader and other "physical" things. I also suspect that it should include a repository object that contains the list of scanned objects.  This repository object could play different roles in different contexts. (such as Cancel a transaction?)
Context. One context for each use case. You say that one of them is "Scanner asks screen to display a list of scanned items."  Isn't the use case "Display a list of scanned items" and the above is the implementation you have chosen with a context? Are there other use cases with roles that map to the scanned items? If not, why should you display them?
Interaction (The role scripts). Role scripts may be accessors to the state stored in the roleplayer object without adding its own behavior. (I've looked at my BabyIDE examples: BB4, a simple planning example, has 2 roles with own behavior and 3 with accessors only.  BB5, the old bank example, has 2 roles with own behavior and 1 accessor (The AMOUNT). BB6, which pays many bills, has 1 role with own behavior and 1 accessor to the above BB5Bank context!  And so on.) Summary: A role does not need to have its own behavior. In trygve, accessors need be explicit, in BabyIDE many roles have no role methods since they may be accessed directly from other roles.
Am I on the right track, or have I missed your point altogether?
--Trygve
--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

--

The essence of object orientation is that objects collaborate  to achieve a goal.
Trygve Reenskaug      
mailto: try...@ifi.uio.no
Morgedalsvn. 5A       
http://folk.uio.no/trygver/
N-0378 Oslo             
http://fullOO.info
Norway                     Tel: (+47) 22 49 57 27

Quang

unread,
Mar 6, 2017, 11:01:25 PM3/6/17
to object-composition
I am re-reading Trygve's papers.
This might be related to the topic.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composition+unsub...@googlegroups.com.

To post to this group, send email to object-co...@googlegroups.com.
Visit this group at https://groups.google.com/group/object-composition.
For more options, visit https://groups.google.com/d/optout.

Andreas Söderlund

unread,
Mar 7, 2017, 4:08:07 PM3/7/17
to object-composition
Thanks everyone for your feedback! Now when I have some decent code to show, I think we can get a bit deeper into the problem.


Quang, good point with the validation method, it had many arguments, but the main problem is that the keypad is asynchronous, so when it's event-driven I prefer to pass the arguments along. The keypad is part of the screen actually (it's a touch screen), but I eventually ended up making that its own role anyway. And I reversed the validation, so the card scanner will wait for the pin before validating the card. That shortened the arguments as well.

Asynchronous behavior has a way of complicating things. For example, there needs to be a loop detecting if the card has been removed, which forces some safety-checks across the Context.

"Ask for info vs. passing control" is the issue at hand for me. It's not easy to get right. It turned out a bit better though, when I changed the screen Role into a state accessor only, and let the other Roles create the behavior. Check it out and let me know what you think!

Matthew and Tryvge, I'm still not sure about turning scannedItems into a Role. If you search the code for it, there are very few places it's being used. Too few? Since the scannedItems is transient (resets for every new borrow 'session'), I don't really see it being a part of some Data model either. If you still think so, please elaborate, I'd like to know more.

Trygve, you asked if "Display a list of scanned items" is a use case. I have plenty of "Display" RoleMethods currently, all they do is update the screen view, so I don't know. It seems too simple again?

About having Roles as only accessors, I remember a discussion we had here, I think I'm paraphrasing Cope here:

Contract fields should only be accessed from the Role's own RoleMethods. This enables the ability to trace the flow of cooperation between Roles, instead of any Role being able to call another Role's underlying object at all times. It's a helpful separation between the local reasoning of how Roles interact locally with their object, and how Roles interact with each other. This helps reading and understanding the use-case-level logic of a Context.

/Andreas

Matthew Browne

unread,
Mar 7, 2017, 10:04:13 PM3/7/17
to object-co...@googlegroups.com
On 3/7/17 4:08 PM, Andreas Söderlund wrote:
Contract fields should only be accessed from the Role's own RoleMethods. This enables the ability to trace the flow of cooperation between Roles, instead of any Role being able to call another Role's underlying object at all times. It's a helpful separation between the local reasoning of how Roles interact locally with their object, and how Roles interact with each other. This helps reading and understanding the use-case-level logic of a Context.
I think your original scannedItems.isAlreadyScanned() method is in keeping with this concept:

    public function isAlreadyScanned(rfid : String)
        return self.exists(function(item) return item.rfid == rfid);

    ...
   
if (scannedItems.isAlreadyScanned(rfid))

But since it is so simple, I agree that your latest version works just as well, despite being less role-oriented:

    var alreadyScanned = scannedItems.find(function(item) return item.rfid == rfid);
    if (alreadyScanned != null)


I think we can be flexible here; DCI lets you model all object interactions down to a very granular level, but sometimes it's simpler and just as readable to use existing class interfaces directly without making everything a role. So while I stand by my earlier assertion that scannedItems would be a perfectly valid role, I think the second version is equally readable (thanks to the alreadyScanned variable).

Regarding my other comments, here's another way of thinking about it...If you wanted to see a visual representation of the object interactions, either mentally or in an IDE, which objects would you want to see? Is the interaction between scanner and scannedItems significant enough that you'd want to see it in the visualization? The answer is probably subjective, but I think there are certainly cases where the answer would be "yes" even if one of the participants in the interaction fell short of a role with significant context-specific role behavior. Writing anemic role declarations for all such cases probably isn't the answer. (However, naming is important, so even writing a forwarding method for the sole purpose of renaming it in terms of the role could be useful - by "anemic" I mean a role with literally nothing other than a couple of exposed instance methods.) Just something to ponder for the future...we can discuss it more later.

Egon Elbre

unread,
Mar 8, 2017, 3:12:32 AM3/8/17
to object-composition
On Tuesday, 7 March 2017 23:08:07 UTC+2, Andreas Söderlund wrote:
Thanks everyone for your feedback! Now when I have some decent code to show, I think we can get a bit deeper into the problem.


Quang, good point with the validation method, it had many arguments, but the main problem is that the keypad is asynchronous, so when it's event-driven I prefer to pass the arguments along. The keypad is part of the screen actually (it's a touch screen), but I eventually ended up making that its own role anyway. And I reversed the validation, so the card scanner will wait for the pin before validating the card. That shortened the arguments as well.

Asynchronous behavior has a way of complicating things. For example, there needs to be a loop detecting if the card has been removed, which forces some safety-checks across the Context.

I would try using an event queue instead of callbacks.

Andreas Söderlund

unread,
Mar 8, 2017, 4:40:02 AM3/8/17
to object-co...@googlegroups.com
ons 8 mars 2017 kl 09:12 skrev Egon Elbre <egon...@gmail.com>:

I would try using an event queue instead of callbacks.

Great, looking forward to that.

/Andreas

James O Coplien

unread,
Mar 8, 2017, 4:54:53 AM3/8/17
to object-co...@googlegroups.com
I think you would get some insight if you developed a set of change cases. Look at the dominant commonality across the various versions of the software as you anticipate it evolving, and use that as a backdrop of variability. In a good design the administrative units are exposed for change, and they encapsulate such change — kind of a generalization of the open / closed principle.

You can evaluate alternative architectures against these criteria and it might offer insight into what is better. Which architectures articulate (express, rather than burying) the points of change, and which ones are best invariant across the anticipated changes?


--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.

Trygve Reenskaug

unread,
Mar 8, 2017, 4:58:01 AM3/8/17
to object-co...@googlegroups.com
My work situation does not permit me to learn another language, so I can't give meaningful comments on your code.  Have you described your use cases and design anywhere?
--

Egon Elbre

unread,
Mar 8, 2017, 7:19:05 AM3/8/17
to object-composition, cisc...@gmail.com
Just to clarify, it was just an idea what would probably make the code clearer. Unfortunately, I don't have the time to do the full implementation.

The basic idea of event queue:

void main() {
while(event = nextEvent()){
if(event instanceof CardInserted){
login(event.card);
} else {
// ignore, or log
}
}
}

void login(card Card) {}
while(event = nextEvent()){
if(event instanceof PinEntered){
if(valid){
scanItems(card);
break;
} else {
invalidAttempts++;
}
} else if (event instanceof CardRemoved) {
// tear-down
break;
}
}
// show goodbye message
}

void scanItems(card Card) {}
while(event = nextEvent()){
if(event instanceof ItemScanned){
items.add(event.item)
} else if (event instanceof CardRemoved) {
// tear-down
break;
}
}
}

Of course, adjusted to fit DCI.

Although, now to thinking about it... it might have some problems with interactive ui.

+ Egon

Quang

unread,
Mar 8, 2017, 9:08:17 PM3/8/17
to object-composition
Async is the beast, and it is hard to tame. I occasionally have it in my context and it destroys the readability.
DCI capture the interaction of objects. 

DCI programmers always go back to mental model when we have issue :)
And if we use the conversation as a metaphor for that interaction.
DCI now focuses on synchronous face to face discussion (or continuous talk over phone) like Remote function calls (but this is cross system communication).

When we have async, it likes talking to a person on the phone, then we stops for a while and he will call back later.
The call back later is interesting. It can be a short so we can wait and we don't need to stop the call.
Or it can be long, and we have to stop the call to do something else.

This reminds me The erlang movie:   https://www.youtube.com/watch?v=3R-ROJwFhns

For short wait I think we can model it as sync call and the caller must wait.
    eg. should I wait for user to choose options in the pop up dialog or not

For the long wait, I think as a general rule it would be a new trigger for a separate context (interaction/conversation)
I think that is the issue with event driven, the conversation is fragmented to too many small conversations ( small talks :) )
And I am not sure it is good or bad thing, but I am sure it is hard to read and understand the code.

Back to the code, it looks to me that we need sub-context here to help.
But I need more time to read the code and try it out.

Btw, thank you very much for very good code example as usual.

/quang

James O Coplien

unread,
Mar 9, 2017, 5:18:23 AM3/9/17
to object-co...@googlegroups.com
It’s certainly part of my mental model.

If you have no Role that represents the association of scanned items, how do I bring this knowledge to the case case level? It would make sense not to have such a list if all I needed from each scanned item was its predecessor and successor. But that artificially imposes an ordering on the items and as such is not faithful to the analysis.

Given my mental model, it’s a Role.

Andreas Söderlund

unread,
Mar 14, 2017, 12:33:18 PM3/14/17
to object-co...@googlegroups.com
That's true, the list has some functionality that should warrant a Role, for example you need to check if it's already scanned.


Also, I've added the other use case as a nested Context, and did some cleanup and improved RoleMethod naming. I think it's ready for a demo, so here you go: https://ciscoheat.github.io/haxedci/

The PIN code is 1234. The printer isn't implemented yet, but there is plenty of interaction still.

Adding descriptive RoleMethods is absolutely great for readability. Except for variable type associations, Haxe is quite similar to trygve, so I hope you can follow the code and comment on the readability, and also how you think this kind of high-level use case matches with a DCI Context.

/Andreas


Andreas Söderlund

unread,
Mar 18, 2017, 7:52:33 AM3/18/17
to object-composition, cisc...@gmail.com
The example is official now, with an in-depth explanation at https://github.com/ciscoheat/haxedci-example

My goal has been to make a somewhat larger example, and also showing how DCI works together with MVC. Let me know what you think about it!

Reply all
Reply to author
Forward
0 new messages