Jump to content

Ah, yes, another OO architecture question


Recommended Posts

I seem to be posting ad nauseum these days, so thanks for bearing with me. I finally get to architect a large system and put my lot-o'-smaller-system-practice to good use. This software will be on two different systems, with slightly different configuration so I'm trying to make it easily adaptable to both. The difference will be in the data channels acquired, processed, and their corresponding action when some condition is met.

 

So, for example, I may have a channel and when it is out of a specified range it sets a digital out or closes a relay, or sets another analog voltage etc. My idea was to have a "condition check" class that was injected into a channel data class. That way I could define subclasses of check condition such as range check, frequency check, digital line change check with a must override "condition met" VI. This would allow me, between systems, to easily define new classes for checking data changes and plug them in. I then wanted to also inject a class defining what to do when condition is met. For example, close relay, set digital out, notify user etc.

 

This all seems like it will be extremely flexible which is great, but it breaks down for me due to the fact that it's on a per channel basis. I could process 50 channels and x  number of them could all have a digital out to set based on a met condition. If all those digital outs are in the same DAQmx task, I don't want to make 3 individual calls to the DAQmx write function. I want to compile them all into one message "set these digital outs on this task" with an array of the lines to set so I can call the DAQmx Write function a single time.

 

So what I'm wondering is 1) is my best bet when processing channels to keep track of all the conditions I will set on a per channel basis, then have a method to compile all the ones I can into a single message (i.e. set all these digital lines on this task) or 2) Is there a better way that would greatly simplify this?

 

Thanks, and Happy Easter!

Edited by for(imstuck)
Link to comment

I recently did something pretty similar-- each channel represented by a class, with a variable number of different condition check objects stored in an array in each channel class, all configured via a spreadsheet file. I went the route of create a manager class that would iterate through all the channel  object condition checks and at the end decide what action to take. 

 

My approach would have the condition checks classes send messages to "DO output" actor / loop. The DO output loop holds on to the actual DAQmx task, and it can decide when and how to set the digital outs. You could cause the DO object to set lines only every 100 ms or whatever, and then at that time determine what to do based on what messages it received in the last 100ms

  • Like 1
Link to comment
My approach would have the condition checks classes send messages to "DO output" actor / loop. The DO output loop holds on to the actual DAQmx task, and it can decide when and how to set the digital outs. You could cause the DO object to set lines only every 100 ms or whatever, and then at that time determine what to do based on what messages it received in the last 100ms

 

Exactly what my coworker and I decided this morning. Buffer all the messages, and every TBD number of ms compile them all and write them.

Edited by for(imstuck)
Link to comment

Another possibility:

Look up "visitor pattern"... it's covered on the LV OO design patterns page over on ni.com.

 

Basic idea: One "Do On DAQmx" object "visits" each object in your array and collects information about the task(s) to be performed. After visiting the entire array, it does the actions. For any objects that are not DAQmx objects, it tells them to go ahead and perform their action immediately.

 

Various variations on this theme exist. One might be the right solution for you.

Link to comment
Another possibility:

Look up "visitor pattern"... it's covered on the LV OO design patterns page over on ni.com.

 

Basic idea: One "Do On DAQmx" object "visits" each object in your array and collects information about the task(s) to be performed. After visiting the entire array, it does the actions. For any objects that are not DAQmx objects, it tells them to go ahead and perform their action immediately.

 

Various variations on this theme exist. One might be the right solution for you.

 

I like this idea. The way I have it now, the DAQmx Task reference is wrapped up in an "Acquistion" class, separate from the incoming array of messages to be iterated on. So would it be kosher to add a method to that Acquistion class "Get Visitor" which returns the appropriate visior with the DAQmx Task reference added to the visitor's private data. This visitor is then used to iterate on all message, gathering the required info, and then writing to the Task at the end. It seems like this would be an OK way of doing it. Or maybe make the acquisition class the visitor, since it already has the task reference.

Edited by for(imstuck)
Link to comment
So would it be kosher to add a method to that Acquistion class "Get Visitor" which returns the appropriate visior with the DAQmx Task reference added to the visitor's private data. This visitor is then used to iterate on all message, gathering the required info, and then writing to the Task at the end. It seems like this would be an OK way of doing it. Or maybe make the acquisition class the visitor, since it already has the task reference.

 

You're the only one who can decide if a design decision is kosher or not.  We can only tell you how we prefer to do it and what some potential consequences might be of the design decision.

 

I don't think there's anything inherently wrong with giving the DAQmx Task to the visitor, if that division of responsibility provides you with the flexibility you need.  It's been a while since I've done stuff with DAQmx and I don't remember the details of how it works, but with any shared resource it's easier to understand if only one "thing" has the ability to use it at any given point in time.  Moving the Task reference between the acquisition object and the visitor object sounds like a bug hatchery.

Link to comment
You're the only one who can decide if a design decision is kosher or not.  We can only tell you how we prefer to do it and what some potential consequences might be of the design decision.

 

I don't think there's anything inherently wrong with giving the DAQmx Task to the visitor, if that division of responsibility provides you with the flexibility you need.  It's been a while since I've done stuff with DAQmx and I don't remember the details of how it works, but with any shared resource it's easier to understand if only one "thing" has the ability to use it at any given point in time.  Moving the Task reference between the acquisition object and the visitor object sounds like a bug hatchery.

 

This is what I went with...using the class that already has the reference as the visitor. I have dug into AQs example on NIs site and am having a little bit of trouble understanding, mostly due to the fact that it doesn't look like the wiki examples (albeit they are text are in text languages). It looks to me basically like the command pattern (I understand they are similar). I don't see anywhere a "visitable" object is being given a visitor, unless I'm just not thinking about it correctly, which is most likely the case . It seems to me some of this may stem from the fact that we can determine at run-time if an object matches a specific type or not (by using to more specific class) so there is no need to have all the polymorphic functions for different visitable types (i.e. the second dispatch). For instance, in his class that is incrementing, he just does a cast and if there is an error because it isn't the correct type, he doesn't call the increment function. I may be off base here, but I am continuing on, getting this to work, and I can come back and adjust as need be later. In the mean time, any further clarification is always appreciated.

Edited by for(imstuck)
Link to comment
This is what I went with...using the class that already has the reference as the visitor. I have dug into AQs example on NIs site and am having a little bit of trouble understanding, mostly due to the fact that it doesn't look like the wiki examples (albeit they are text are in text languages). It looks to me basically like the command pattern (I understand they are similar). I don't see anywhere a "visitable" object is being given a visitor, unless I'm just not thinking about it correctly, which is most likely the case . It seems to me some of this may stem from the fact that we can determine at run-time if an object matches a specific type or not (by using to more specific class) so there is no need to have all the polymorphic functions for different visitable types (i.e. the second dispatch). For instance, in his class that is incrementing, he just does a cast and if there is an error because it isn't the correct type, he doesn't call the increment function. I may be off base here, but I am continuing on, getting this to work, and I can come back and adjust as need be later. In the mean time, any further clarification is always appreciated.

 

 

I have looked at that example before as well, and I don't get it either.

 

In my admittedly poor understanding, it involves a "visitor" class iterating through a collection of objects and performing some operation on each one. The collections could be something as simple as an array or something like a tree or graph of objects. The visitor "knows" how to get to (well, visit I suppose) every object in that collection, and then do whatever it's supposed to do to that object. So in your case, the "thing" to iterate over, is maybe an array of channel classes, each with an array of check classes. And the thing to do is to execute the check and take action if necessary. 

 

I see it like this-- here's some ASCII art: 

x's channel classes, o's are check classes

 

x x x x x x x   channel class array

o o o o o o o   c

o o   o   o o   h

o     o     o   e

o               c

o               k

                a

                r

                r

                a

                y

                s

 

basically the visitor knows how go through the class array, and for each class array go through the check array. Basically nested for loops.

 

AQ is this at all close? 

Link to comment

Here's another Visitor Pattern example that follows the traditional pattern more closely than the NI one. I learned the Visitor Pattern years ago in a compilers class that was taught in Java, and it was an interesting exercise to put together this example in LabVIEW. I apologize for the lack of comments and icons, I put it together rather quickly, but I hope it's helpful and I'm happy to fill in details if necessary.

 

The example contains two class hierarchies: DataItem, and Visitor. The DataItem class has three children: IntegerDataItem, StringDataItem, and DataItemCollection (an array of DataItems). The Visitor class has two children: Generate DataItem Collection, and To Comma Separated String. The first uses the Visitor pattern to build up a DataItem Collection containing random numbers. Strings get negative numbers, Integers get positive numbers. The second visits each DataItem in the collection, and if it's a string or integer it converts to a string (if needed) and appends it to a comma-separated list.

 

Each child of the Visitor class has a method for every child of DataItem (Visit DataItemCollection, Visit IntegerDataItem, and Visit DataItemString). To add a new action, create a new class that inherits from Visitor and implements those three methods. To get familiar with the pattern, here are some ideas for exercises:

- Create a Sum class that adds all the numbers in the DataItemCollection (converting the strings to integers)

- Create a TreeDisplay class that formats the DataItemCollection into a nice format for display, showing the levels of nested collections

- Add a new DataItem type (for example, floating point) and add appropriate classes to each Visitor class.

 

Note that when you want to do an action on the DataItem, you pass the Visitor (action) class to the DataItem in a call to the Do VI. This is the first level of dynamic dispatch. Then, the Do VI calls the appropriate action from the Visitor and passes the DataItem to it, adding a second level of dynamic dispatch. This is why the pattern is also known as double-dispatch.

 

This example is in LabVIEW 2012.

Visitor Pattern Example.zip

Edited by ned
  • Like 1
Link to comment

Thank you Ned for the very nice Visitor pattern example!  The data object of a given type has a do method, that takes in a visitor of whatever type  , and executes a that visitor "Visit" method specific to that data type. (visitIntegerDataType eg) .

 

I can see now how setting things up this way could be very useful. You have visitors that do all kind of different things, but you have to only write the iteration code once-- in this case it's just a for loop in DataItemCollection.lvclass, but I can see that if you had oh, binary trees or oct trees or graphs doing it that way would be awfully useful, since visiting every member there involves a bit more than dropping a for loop on the block diagram. Very cool.. glad I learned something today!

Link to comment
I can see now how setting things up this way could be very useful. You have visitors that do all kind of different things, but you have to only write the iteration code once-- in this case it's just a for loop in DataItemCollection.lvclass, but I can see that if you had oh, binary trees or oct trees or graphs doing it that way would be awfully useful, since visiting every member there involves a bit more than dropping a for loop on the block diagram.

Poke through the code a little bit more - a DataItemCollection is a DataItem that contains an array of DataItems, so it does implement a tree (the reason the visitors are reentrant - they can be called recursively). That's why I mentioned one good exercise with it is to write a visitor that formats the structure into a tree for display.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.