Black Pearl Posted April 10, 2010 Report Share Posted April 10, 2010 For this weekend I decided not to play around with VI Scripting but do learn some LVOOP. What I would like to do is to get the following design into LVOOP, taken from the UML specs: -- 7.3.14 Element (from Kernel) [..] Associations • ownedComment: Comment[*] The Comments owned by this element. Subsets Element::ownedElement. • / ownedElement: Element[*] The Elements owned by this element. This is a derived union. • / owner: Element [0..1] The Element that owns this element. This is a derived union. 7.3.33 NamedElement (from Kernel, Dependencies) [..] Generalizations • “Element (from Kernel)” on page 64 [..] Attributes • name: String [0..1] The name of the NamedElement. • / qualifiedName: String [0..1] A name that allows the NamedElement to be identified within a hierarchy of nested Namespaces. It is constructed from the names of the containing namespaces starting [..] [2] When there is a name, and all of the containing namespaces have a name, the qualified name is constructed from the names of the containing namespaces. (self.name->notEmpty() and self.allNamespaces()->select(ns | ns.name->isEmpty())->isEmpty()) implies self.qualifiedName = self.allNamespaces()->iterate( ns : Namespace; result: String = self.name | ns.name->union(self.separator())->union(result)) -- So in everyday-geek-speak: * The element class needs a reference to an object of type element (the owner). I'm not really sure how to solve this (DVR's?). I can get this not breaking the arrow by using a parent class of element (ElementParent.lvclass) and using this as part of the private data. * If I now try to make the qualified name, I need a) to cast the Owner to the Element child class (ok) b) to recurse through the GetQualifiedName method. (Here I get errors thrown). How would you code up a design like this? Felix 1 Quote Link to comment
Daklu Posted April 12, 2010 Report Share Posted April 12, 2010 What I would like to do is to get the following design into LVOOP, taken from the UML specs: <snip> * The element class needs a reference to an object of type element (the owner). I'm not really sure how to solve this (DVR's?). I can get this not breaking the arrow by using a parent class of element (ElementParent.lvclass) and using this as part of the private data. * If I now try to make the qualified name, I need a) to cast the Owner to the Element child class (ok) b) to recurse through the GetQualifiedName method. (Here I get errors thrown). You're getting an error because you're trying to cast a parent object (ElementParent) into a child object (Element). You can't do that... if the child class has some extra data or methods associated with it the parent object doesn't have any way of handling them. I made the same mistake not too long ago and AQ was kind enough to explain why it doesn't work. For your design I don't think the ElementParent class is necessary. It just makes your design harder. Labview's by-val nature makes two-way knowledge of the parent-child relationship a little harder to implement. There are a couple ways I can think of doing it; using an ElementManager class and setting up your hierarchy in memory directly as you are trying to do. (I think the recursion should work once you quit trying recurse into a different object's method. ) Conceptually I think the ElementManager is a little easier to understand since it creates a clear distinction between element behavior and element relationship behavior. For the ElementManager technique, create an Element class with the following properties and methods: Class Element Properties -string GUID // all objects must have a guid -string ElementOwner // is empty if root node -string[] OwnedElements // is empty if leaf node Methods -string GetQualifiedName -(Also add getters & setters for the class data as needed) The idea is that when an object is created at run time it generates a GUID you can use to identify it. Instead of each element storing its parents and children directly, it stores the GUID of its parent and children. Each element is completely independent and has no real knowledge of the other elements other than their GUID. To manage the relationships between the different elements, create a helper ElementManager class with the following properties and methods: Class ElementManager Properties -element[] AllElements // array to hold all element objects Methods -element GetElementByGuid(string GUID) // returns an element object Given a guid, the ElementManager will search through it's array to find the element with that guid and return that to the caller. Of course if you're going to have very large data sets you can change from an array to binary tree or any other mechanism for storing data. The array implementation is entirely private and can be changed without affecting the callers. I like to have higher level methods in my manager classes and keep GetElementByGuid private. This makes it easier to use and completely encapsulates the guid from class users. A few to consider are: -element GetParentElement(element ChildElement) // returns parent element -element[] GetChildElements(element ParentElement) // returns array of child elements -bool IsParentChildRelationship(element Parent, element Child) // returns true if Parent is a parent of Child -void AddChildElement(element Parent, element Child) // Adds Child as a child of Parent element -element[] GetElementAncestry(element Element) // Returns an array of elements from the root to the parent of this element 1 Quote Link to comment
K-node Posted April 12, 2010 Report Share Posted April 12, 2010 -string GUID // all objects must have a guid The idea is that when an object is created at run time it generates a GUID This is slightly off topic: This VI which came in my installation of LV2009 provides a solution to creating a GUID... C:\Program Files\National Instruments\LabVIEW 2009\resource\Framework\Providers\API\mxLvGenerateGuid.vi After writing my own quasi-unique method, I found this little gem (grrrr-r-r-r-r). It may also be in earlier versions of LV. Is there another VI already native to the LV Functions palette for creating a GUID? I looked at a few hits from searching the LAVA forums but did not see any reference. -kg Quote Link to comment
GoGators Posted April 12, 2010 Report Share Posted April 12, 2010 I may be reading into it a little too far, but is this very similar to a by-directional tree data structure? Each node is self-referencing in the tree. If not then what is the use case for what you are describing? Tree.zip Quote Link to comment
Black Pearl Posted April 13, 2010 Author Report Share Posted April 13, 2010 Thank you all for the feedback. It'll be next week that I'll have the time to look deeper into this. Concerning GUID, I already have xmi:id and I think it's unique for each element=object (and not each type of element=class). So the element manager might be a good idea. The main issue I ran into was that I tried to use the eclipse/java implementation to avoid architecting it all on my own. This was obvious the wrong way... So I was browsing through an abstract factory (so everything was multi-inheritance of interfaces, and no references of the actual implementation) and trying to find the Set-Accesor method for the owners. Well, the 'trick' is that the GetOwnedStuff returns a object inherited from the list class, which does implement an add method. So I'm going to try to rearchitect it towards native by-val. Felix Quote Link to comment
Daklu Posted April 13, 2010 Report Share Posted April 13, 2010 I may be reading into it a little too far, but is this very similar to a by-directional tree data structure? Each node is self-referencing in the tree. If not then what is the use case for what you are describing? Is the bi-directional tree your design? Nice job. The core implementation is similar to what I had imagined for an in-memory tree, though I hadn't gotten around to actually writing any code yet. I think the design can be simplified a bit by replacing TreeObj with Labview Object. I'd also consider changing the design to allow tree operations be performed on nodes. Making node a subclass of tree is the most obvious way; I'm not sure it's the best though. DVRs of objects containing DVRs of objects containing DVRs of objects, etc... it get a bit dizzying trying to sort through it all! Quote Link to comment
GoGators Posted April 13, 2010 Report Share Posted April 13, 2010 Is the bi-directional tree your design? Nice job. Yes. I have been through a few iterations trying to make it as best as possible. The latest makes it so you can save the tree in Native LV XML even though it uses DVRs. It works well for making class upgrades due to LV versioning. I think the design can be simplified a bit by replacing TreeObj with Labview Object. I'd also consider changing the design to allow tree operations be performed on nodes. Making node a subclass of tree is the most obvious way; I'm not sure it's the best though. I thought about it, but it didn't really seem like a node and tree should have the same functionality, more that a tree was an aggregation of nodes. But yes, the overlap did make me consider it. I made it for a possible NIWeek Presentation on alliance day on advanced data structures. The linked list is complete, but the graph is taking quite a bit longer. Just so I know, is a tree what the purpose of your self-referencing object was going to be used for? I know you mentioned namespacing, so I was kind of interested to hear what that was related to. Quote Link to comment
Daklu Posted April 13, 2010 Report Share Posted April 13, 2010 <Apologies to Black Pearl for completely hijacking his thread... > A possible presentation? Was your proposal accepted? If I may be so bold as to offer some feedback... I think some of the functionality in your Tree api is a little disjointed, providing good high level methods for those people who need to do exactly what you provide while possibly leaving other developers with more difficulty in accomplishing what they need. For example: -duplicateBranch.vi creates a new branch and adds it as a sibling of the branch it is copied from. What if I want to copy a branch and add it to a different node? Or what if I want to copy a branch and make a new tree out of it? -readTreeFromFile and writeTreeToFile combine the process of flattening the tree to xml and writing the xml to disk. I (as your api user) want to flatten the tree to xml and store the xml string in memory to support undo actions. -SaveTree and LoadTree each pop up dialog boxes requesting user input. I'm planning on using this api in a remote system where users might not notice a dialog box for days. How can I make it just log an error if the path input isn't correct? -I see a Node class and a Tree class, but some of the Tree vis refer to "Branches" yet have Node inputs and outputs. How is a Branch different from a Node? (The answer, I now realize, is nodes and branches are the same thing. I actually asked myself this question when first looking through the code because I expected different functionality for different terminology.) -graftBranch adds a single branch to a single node. graftBranches adds multiple branches to a single node. Where are the methods to add a single branch to multiple nodes or multiple branch to multiple nodes? (That's a trick question... I really don't want those methods. I think graftBranches is unnecessary.) If your example is intended to be reusable code, I think you'll be better off providing a small set of fundamental capabilities and let the users combine those fundamentals into higher level functionality that makes sense to them. Really all the tree needs is three core functions: addNode, removeNode, and copyNode. Duplicate and relocate are easy enough to implement from those three methods. If you want to support persistence add flattenToXml and unflattenFromXml. Leave it to the class user to decide how the objects will be persisted. (Disk, memory, audio file of morse code...) On the other hand... if it's an example of one way to implement a bi-directional tree, you can ignore everything I just said. -------------- There are a couple things I haven't figured out yet. First, what's the purpose of storing subnode Class names? How is it intended to be used? Second, the Tree api operations need a NodeID. Is the api user supposed to assign IDs to nodes when the nodes are created? When I graft a branch I created, the NodeIDs are potentially reset. How am I expected to retrieve my nodes if they have been changed? Quote Link to comment
GoGators Posted April 14, 2010 Report Share Posted April 14, 2010 I am so glad you asked these questions because they all are relevant. Because hijacking threads is never a good way to present information, I will start a new thread with what I intend to present. <Edit> So I started to repost and realized my code was not yet ready to warrant a new topic. Here are the answers to your awesome input but note that everything is not yet ready so I'll do my best. A possible presentation? Was your proposal accepted? Yes. For alliance day the systems engineers get to put on some topics of relevance. Mine will involve Machine Control with use of linked lists, trees, and graphs via LVOOP and DVRs. -duplicateBranch.vi creates a new branch and adds it as a sibling of the branch it is copied from. What if I want to copy a branch and add it to a different node? Or what if I want to copy a branch and make a new tree out of it? Yup. Can do, but my intention was for configuration editors where that is common. Maybe a better solution is to just duplicate and leave it up to the user whether to add it back on or make it its own tree. -readTreeFromFile and writeTreeToFile combine the process of flattening the tree to xml and writing the xml to disk. I (as your api user) want to flatten the tree to xml and store the xml string in memory to support undo actions. Good call. It is there but I should make it more evident. -SaveTree and LoadTree each pop up dialog boxes requesting user input. I'm planning on using this api in a remote system where users might not notice a dialog box for days. How can I make it just log an error if the path input isn't correct? Once again, didn't think about that case. I tried to keep the API easy, and missed it. -I see a Node class and a Tree class, but some of the Tree vis refer to "Branches" yet have Node inputs and outputs. How is a Branch different from a Node? (The answer, I now realize, is nodes and branches are the same thing. I actually asked myself this question when first looking through the code because I expected different functionality for different terminology.) The goal is to move the API to use the terminology of branches. Nodes should be independent and "floating" and thus rarely used. But branches indicate structure. -graftBranch adds a single branch to a single node. graftBranches adds multiple branches to a single node. Where are the methods to add a single branch to multiple nodes or multiple branch to multiple nodes? (That's a trick question... I really don't want those methods. I think graftBranches is unnecessary.) Graft branches was added for a specific reason. Assigning IDs (or GUIDs) and then assuring they were unique to the the tree was very costly speed wise. The act of graftBranches would assign IDs at he beginning of the operation to eliminate redundate searches for unique IDs. If your example is intended to be reusable code, I think you'll be better off providing a small set of fundamental capabilities and let the users combine those fundamentals into higher level functionality that makes sense to them. Really all the tree needs is three core functions: addNode, removeNode, and copyNode. Good call, but for the presentation I need application rather than "Look what I can do" Those functions are common for a configuration editor, thus they were included. </Edit> Thoroughly high-jacked now. Quote Link to comment
Black Pearl Posted April 14, 2010 Author Report Share Posted April 14, 2010 I don't mind the 'hijacking'. Felix Quote Link to comment
Daklu Posted April 14, 2010 Report Share Posted April 14, 2010 I am so glad you asked these questions because they all are relevant. *Phew*... an uninvited critique of someone's code, no matter how well-intentioned, is a good way to make enemies. I almost didn't post at all. Also, I'm posting this in a "these are my thoughts and ideas, what do you think?" way, not a "I know how to do it and you don't" way. I hope it comes across correctly. -duplicateBranch.vi creates a new branch and adds it as a sibling of the branch it is copied from. What if I want to copy a branch and add it to a different node? Or what if I want to copy a branch and make a new tree out of it? Yup. Can do, but my intention was for configuration editors where that is common. Maybe a better solution is to just duplicate and leave it up to the user whether to add it back on or make it its own tree. ... I tried to keep the API easy, and missed it. Over the past year or two, there are three things that have dramatically improved my code: 1. Studying OO design patterns. 2. Focusing on creating modular and layered applications. 3. Designing (or attempting to design) my code modules to have good APIs. The last is, IMO, the hardest to do. Bad APIs in reuse code kills any motivation to reuse it. Bad APIs in applications make the application hard to read and maintain. A while back I read a book titled Practical API Design: Confessions of a Java Framework Architect. (Check it out from the library if you can--I didn't think it was applicable enough to LV to warrant the purchase price.) It had some interesting insight into the issues this architect had in creating the Netbeans framework. Here's a good summary of several API design guidelines. Before reading the book I used to try and include everything a class user might need, figuring the convenience would be helpful. In reality it just confused me when I went back later and tried to use the class. I have since learned a better approach: Step 1: Make the api as small as possible, but no smaller. This means I create all the fundamental methods a user would need to do to perform any foreseeable operation. For a general Tree object, addNode and removeNode are the essential operations. Assuming the node methods are public the user can use these two methods to do anything. Step 2: Make the common things easy while keeping the difficult things possible. This is where the convenience methods are added. The important thing to keep in mind is to your convenience methods should be useful to a majority of your users. Writing convenience methods for a specific subset of users just complicates the api for everyone else. The convenience methods should also be significant, wrapping up frequently needed operations, operations that would otherwise be cumbersome to implement, or operations that would require exposing the implementation. So what are the operations most Tree users will need to do? It's reasonable to expect users will need to retrieve nodes without actually removing them, so copyNode should be in there too. (Though they are getting a reference to the node itself, not a copy of the node, so I would change the name to getNode.) Iterating through the tree is a useful and expected capability. I haven't figured out the best way to iterate through generalized trees, but I suspect it involves an iterator class. Serializing (persisting) the tree could probably be argued either way. In general tree persistence probably isn't all that common, but the convenience of having the method there is worth something and it's unlikely to confuse many users. These methods make up your BaseTree class. Since you want to use a tree for configuration editors, subclass a ConfigurationEditorTree class and add the methods that are common for those particular kinds of trees. Load, Save, DuplicateBranch, etc. Whether or not you include dialog boxes in the Load and Save methods is largely personal preference. As long as ConfigurationEditorTree is application-level code as opposed to reuse-level code there's not really any harm in including them there. I'm not quite sure exactly how you expect your users to use the tree, so I can't offer any additional comments on that. The goal is to move the API to use the terminology of branches. Nodes should be independent and "floating" and thus rarely used. But branches indicate structure. The problem with this is that the nodes are not independent. Any given node may have parent and/or child nodes; therefore any given node could have the structure you want associated with branches. As I understand the api now, users will need to create nodes before adding them to the tree. Users could also create their own branch using the node linking methods. If the nodes are intended to be independent perhaps the methods used for linking nodes should be community scoped? Graft branches was added for a specific reason. Assigning IDs (or GUIDs) and then assuring they were unique to the the tree was very costly speed wise. The act of graftBranches would assign IDs at he beginning of the operation to eliminate redundate searches for unique IDs. The need for a separate method to add multiple branches could indicate a problem with your implementation. If each node must have an independent ID, are there other ways you could obtain one rather than iterating through through the tree every time? GUIDs are presumably unique. Could you simply have the OS generate one? What about using the DVR refnum? Using the node's tree path? Is a unique ID even necessary? It's not clear to me what it's purpose is. The ElementManager technique above needs the ID to find the correct object in the array when, for example, asking for a parent node. In your by-ref design asking for a node's parent node returns the parent node--so why the ID? Quote Link to comment
Black Pearl Posted May 1, 2010 Author Report Share Posted May 1, 2010 I'm back for the OOP training lessons. On this rainy afternoon, I did concentrate on the question of how to do by-ref implementations. My first approach (and I see the same in the Tree code from GoGators) was to put the object into the DVR. This seems to be a direct translation from other OOP languages with their pointer based by-ref concept. So today I realized that I also can place the data inside a DVR, make the object private data only the DVR and have the referencing/dereferencing inside the accessor methods of that object. This looks much better, as I 'hide' the by-ref implementation from the user (means they need to know nothing about DVR and IPE and less effort to code because they need to be coded only once). The self-referencing seems to work the same way via a parent class (placed inside the data TD inside the DVR inside the private data). Do these two approches to by-ref differ considerably? I don't want to consider for now all the fine details where I can get race-conditions or deadlocks, for this I would just like to have a general judgement approach A or B is more robust. If the weather won't change tomorrow, I'll try to face my problems with this way of coding. Felix Quote Link to comment
Black Pearl Posted May 1, 2010 Author Report Share Posted May 1, 2010 It is still raining, so I was doing some code before going out. One thing seems to be a bit cumbersome in LV. In a traditional language I can define a class <list> with all kind of add, clear, ... methods and use that as the type for the accessor Get operation. So I can write something like: MyCollection.GetList.Add(Element) Using the by ref implementation, it seems that I need to either a) rewrite the array as an list object (gives a bad performance). b) provide a class locking (semaphores) so I can code lock, checkout, modify, checkin, unlock c) write the add, clear ... methods for each 'list' Any better approaches. Felix Quote Link to comment
K-node Posted May 3, 2010 Report Share Posted May 3, 2010 My first approach (and I see the same in the Tree code from GoGators) was to put the object into the DVR. This seems to be a direct translation from other OOP languages with their pointer based by-ref concept. So today I realized that I also can place the data inside a DVR, make the object private data only the DVR and have the referencing/dereferencing inside the accessor methods of that object. This looks much better, as I 'hide' the by-ref implementation from the user (means they need to know nothing about DVR and IPE and less effort to code because they need to be coded only once). I prefer the data inside a DVR. As you noted, this does make for a very clean interface. I generally define a single strict type data as a "Strict Type Def." control. This thread, if you have not reviewed it yet, has some good information: http://lavag.org/top...iew-oop-by-ref/ The self-referencing seems to work the same way via a parent class (placed inside the data TD inside the DVR inside the private data). LabVIEW is clever that way. I find it a pain to deal with the casting ("To More Specific Class"), but all of this can happen in a "Get Sibling" method or some such thing. Do these two approches to by-ref differ considerably? ...a general judgement approach A or B is more robust. Nuances of race conditions, etc. aside, I am not sure if one is more robust than the other. In my project, I used the inner DVR approach. Though this requires a 'Create' and 'Destroy' method which are just begging for some sort of automation via scripting, I still find the approach very clear especially when I actually get to use the class. -Kurt Quote Link to comment
K-node Posted May 3, 2010 Report Share Posted May 3, 2010 (edited) You are really getting into this OO stuff. Very nice! a) rewrite the array as an list object (gives a bad performance). I have implemented this approach and I will admit that performance was not a design constraint. However, I would think that the performance impact can be minimized based on the implementation. First, if you implement a <List> class where it itself has a DVR with the data, then the 'owning' class does not have to have the <List> object in its DVR. This eliminates one de-reference IPE. This assumes that you don't mind having a mix of DVR and non-DVR data in an object's private data cluster. For my basic <List> class is is basically a wrapper around LV's own Array. I have added the method call overhead for Add, Remove, etc. so there is that hit on performance. But if the <List> is really a collection of objects, then I can pass a 'target object' into the wrapper representing "Index Array" to perform the "Preserve Runtime Class" method and have the method return the entity in the list correctly cast. b) provide a class locking (semaphores) so I can code lock, checkout, modify, checkin, unlock I can see a use for this even if "a)" was implemented. Locking access to the list would suspend other threads from either modifying or obtaining stale data. I am assuming that the checkout method would return the 'Array' and the checkin would accept it. c) write the add, clear ... methods for each 'list' I do not like this approach from the replication of code. And are you just wrapping the 'Array' methods? This does not seem to eliminate the performance lost there. Any better approaches. I've got nothing. Keep up the questions and share your experiences. My implementations have worked for me, but I am currently a sole developer at least in LVOOP here. My background is in text based languages C/C++/C# and only re-started programming in LV last June (after a 20 year hiatus). -Kurt Edited May 3, 2010 by kugr Quote Link to comment
GoGators Posted May 3, 2010 Report Share Posted May 3, 2010 Do these two approches to by-ref differ considerably? Felix The main reason why I place the entire object into the DVR instead of just the object properties is because I am working on recursive functions. In particular I call method X on object A. Object A has a ref to the next Object B, so then method X recurses on object B, and so on and so on. If the DVR only encapsulates the data and not the entire object, I don't think you can do that. But yes, the casting of DVR references is annoying. Quote Link to comment
K-node Posted May 3, 2010 Report Share Posted May 3, 2010 (edited) The main reason why I place the entire object into the DVR instead of just the object properties is because I am working on recursive functions. In particular I call method X on object A. Object A has a ref to the next Object B, so then method X recurses on object B, and so on and so on. If the DVR only encapsulates the data and not the entire object, I don't think you can do that. I am not sure that limitation exists, at least in how I implemented this concept. Yes, the DVR only 'encapsulates' the data, but one of the pieces of data is the sibling object. So inside Object A's method X, we dereference Object A's data getting its sibling object (Object B) and call its method X. As Felix encountered, LV will not allow me to actually put an object of its own type in its private data cluster (that would exist in your approach too *see edit). So, you need to have a parent object. It could be the LV Object, but then I would have to send it through the 'to more specific class' function. In my recursion, I created a parent object with method X supporting dynamic dispatch (and reentrant execution). Personally, I prefer the 'DVR the object' approach. The constant need to dereference made it to cumbersome. The only way to encapsulate was to add a wrapper layer and that was a non-starter. -Kurt edit: I think I am wrong about the need in your approach. The refnum is probably hiding the fact that the object is the same type. edit2: I thought again. You still need some parent object. Edited May 3, 2010 by kugr Quote Link to comment
Daklu Posted May 5, 2010 Report Share Posted May 5, 2010 Kugr! Welcome back! I haven't seen you since last fall... thought maybe you and SciWare fell of the edge of the world. Do these two approches to by-ref differ considerably? Functionally I don't think there's that much difference between the two, though I have not spent a lot of time playing with the object-in-the-DVR technique. Essentially it comes down to how you expect to use the class in your applications. If you're okay with that class *forever-and-always* being a by-ref class, I prefer putting the class data in a DVR and hiding those details from the class user. If I'm not sure how the class will be used I leave it a regular by-val class--the user can always put it in a DVR if they need an instance to be by-ref. In a traditional language I can define a class <list> with all kind of add, clear, ... methods and use that as the type for the accessor Get operation. You're talking about generics, right? Labview, being a strictly typed language, doesn't support them. I was talking to AQ once about using some of the techniques in the Interface framework to explore generics in Labview. I believe the term he used was "hopeless." I'd love to have them someday, but I'm not terribly hopeful. If pure speed is your objective you'll have to write a unique collection for each data type you want to collect. So I can write something like: MyCollection.GetList.Add(Element) Using the by ref implementation, it seems that I need to either a) rewrite the array as an list object (gives a bad performance). b) provide a class locking (semaphores) so I can code lock, checkout, modify, checkin, unlock c) write the add, clear ... methods for each 'list' For clarity I'll separate your apparent objectives into 1) creating a collection, and 2) making it by-ref. Part 1 I'm not quite following what behavior you expect from your collection. MyCollection.GetList implies that you could also have MyCollection.GetSet, MyCollection.GetTree, and maybe a MyCollection.GetLinkedList methods. The problem is that the data you put in a list collection may not translate well into sets, trees, linked lists, bags, etc. I experimented with a unifying parent collection object for while and decided it wasn't worth the effort. I've since decided that I expect my collection users to know what kind of collection they need for their data. As a starting point I've settled on the following kinds of collections: Bag - Not ordered, duplicates allowed Set - Not ordered, no duplicates List - Ordered, duplicates allowed OSet - Ordered, no duplicates These are my four basic collections. Note that these just define the behavior of the collection. They do not define the collection's data structure--i.e. how the collection is implemented. Each can be implemented as a tree, linked-list, array, hash table, etc. The implementations are interchangable. I've attached an *incomplete* implementation of my Bag collection. So for your collection what kind of behaviors are you looking for? Part 2 It appears you want your collection, as opposed to the objects in the collection, to be by-ref. Assuming this is application code rather than reuse code, create a by-val collection object and verify it works correctly. Wrap it in another class that keeps an instance of your collection object in a DVR. Done. Locking is not typically something that is implemented in collections. If you need to lock your collection I think adding simple Lock and Unlock methods would suffice. I would leave it up to the collection user to lock and unlock the collection rather than trying to build it into the collection code. Brief Explanation of Bag Library The project has two classes, Bag and BagImplementation. Bag provides the public api to the functionality implemented in the BagImplementation class and its derivatives. As you can see I convert all the keys into variants and upcast the objects in the collection to LVObject. If I want to ensure type safety or relieve the Bag user of the nuisance of downcasting the object when they retrieve an item I create a wrapper class for Bag and do the necessary type conversions there. (The wrapper class, MyObjectBag, is not a child of Bag. That would allow clients to use Bag methods on MyObjectBag and defeats the attempt to simplify the api.) BagImplementation uses simple arrays (and the build array prim) to keep track of the keys and items. If I need an alternative implementation I derive an child class (BinaryTreeBagImp) and override all the methods with the new implementation. Then, in my application, I use Bag.SetImplementation to change from a simple array to a binary tree. Note that changing implementation effectively erases all the data in the collection. I just yanked this out of existing code the other day and I've been working on cleaning it up and packaging it for VIPM. The Set, List, and OSet collections are still on my to-do list, as are more data structure implementations. I have not yet figured out if it makes sense to write the implementations in a way that allows them to be used for different kinds of collections. If anyone wants to collaborate on reusable collection modules let me know. (And, seeing as how I freely critique others' code, I expect no less in return. Let me have it! ) BagLibrary.zip Quote Link to comment
K-node Posted May 6, 2010 Report Share Posted May 6, 2010 Kugr! Welcome back! I haven't seen you since last fall... thought maybe you and SciWare fell of the edge of the world. Thanks, Daklu. I have been lurking about, but have not posted. I have been immersed in my project which is heavily 'class data in a DVR' based. And I use interfaces, lots of interfaces, perhaps too many. It is very similar implementation to what you proposed some time back. I have not seen Jim Kring post is a bit either. He was a pretty early adopter of LVOOP. The project is essentially over - a prototype of sorts showing that we can do it if we need to. So it goes on the shelf for a bit while I write up an internal report on the implementation. There is very little knowledge on OOP for automation so part of the report is some fundamentals. (And, seeing as how I freely critique others' code, I expect no less in return. Let me have it! ) I always liked your programming style and your justifications for your approach - I still do. I was disappointed that the additional ornaments on the VI icons were missing (remembering the cell phone and baby example). Because I am so focused on 'DVR the data', I would have DVR'd the data somewhere. My first thought was to do this in the Bag class. Then I thought again and I think I would have the DVR in the BagImplementation class. This would hide the Create and Destroy methods (needed to allocate the DVR and release it) from the user of Bag. This also means that BagImplementation needs Dynamic Dispatch methods of Create and Destroy. They would be no-ops in general. In my project, I started on an idea of IMemberVarsDVR interface and that a factory that created a new instance of any class would attempt to cast the object to this interface and if successful would call the Create method. This was late in the project and was not worth the change. Anyway, I could see something similar working here. Not sure that is the right approach though. If anyone wants to collaborate on reusable collection modules let me know. It would be very cool to also make this part of a large® framework. I am trying to take my group into the OOP realm and need to make things as easy as possible. I'd be willing to help out on this. We should probably start a separate thread. If others join in, that would be great. Kurt Quote Link to comment
Genki Posted May 7, 2010 Report Share Posted May 7, 2010 I may be reading into it a little too far, but is this very similar to a by-directional tree data structure? Each node is self-referencing in the tree. If not then what is the use case for what you are describing? Dear GoGators I like your UML approach. So I wanted to check your Tree example code. However in the project one file was missing (TreeObj.lvclass:Message.ctl) Could you add this in the zip file? Thanx. By the way: Your diagramm look like they were created with StarUML. Correct? Is this your preferred UML tool? Quote Link to comment
GoGators Posted May 7, 2010 Report Share Posted May 7, 2010 Dear GoGators I like your UML approach. So I wanted to check your Tree example code. However in the project one file was missing (TreeObj.lvclass:Message.ctl) Could you add this in the zip file? Thanx. Just delete message.ctl. It doesn't do anything. By the way: Your diagramm look like they were created with StarUML. Correct? Is this your preferred UML tool? StarUML is Daklu's suggestion, and I like it so far for the class diagram. There is apost somewhere around here about everyones favorite UML tool. Quote Link to comment
Black Pearl Posted May 7, 2010 Author Report Share Posted May 7, 2010 Functionally I don't think there's that much difference between the two, though I have not spent a lot of time playing with the object-in-the-DVR technique. Essentially it comes down to how you expect to use the class in your applications. If you're okay with that class *forever-and-always* being a by-ref class, I prefer putting the class data in a DVR and hiding those details from the class user. If I'm not sure how the class will be used I leave it a regular by-val class--the user can always put it in a DVR if they need an instance to be by-ref. That makes perfect sense to have the two approaches! 1) If I'm the class designer and in need for by-ref -> private-data into the DVR 2) If I'm the class user and need to use a by-val class as by-ref -> object in the DVR 1) allows the class user to be ignorant of DVRs, IPEs, ... 2) allows the class user to be very flexible with objects, like have all the init code by-val and just when he needs to branch the object for parallel task operation, place it into an DVR. After parallel execution, dereference back and operate by-val again. So they are not two flavours of implementing the same by-ref. But they are two different use cases: make a class by-ref or use an object by-ref. Because I implement an design that is completly traditional (I refere to JAVA, C++) by-ref, I get better code making the class itself by-ref. In other situations I might be better off using by-val or object-inside-DVR. For my current implementation, I will consider the object-inside-DVR design for the package class. I will try a brief explanation of the model: Almost every class is inherited from the 'namedElement' class. This defines mustBeOwned=true. The only class that overrides this is Package (generalization namespace, mustBeOwned = false). Because the package is the only class the is 'creatable' by the class user (I use this term as you find it when using ActiveX e.g. to to interface Excel, where you need to obtain the workbook ref from the Applications Workbooks Collection via some methods like Add, New), you can implement factory methods like CreateClass(name, classType) (the eclipse uml2 implementation does this). Using the factory design pattern, you can hide the Create and Destroy methods from the class user. General idea for both by-ref designs: It is a bit cumbersome to do all the repeated DVR/IPE/UnBundle operations by hand. Some will be faster using Reusable VIs with the drop content flag set, some will go as templates, others might be benefit from scripting (like an 'Create Wrappers'-tool for the object-inside-DVR). uml-tools: - I still don't use StarUML although I've installed it. - For fast scetches, I still think dia is the way to go (especially as they have also support for other domains, if you don't have a specific tool and/or just need a scetch, it is easy). - For design I'm trying to get used to eclipse. They have a design and modelling branch (I think this equals the galileo package). But it comes with a lot of overhead = full support of project management tools far advanced what you have with your LabVIEW installation (+ all kinds of integration with SCC, issue tracker, ...). I think if you have the need for these things, you'll already go with the toolkits from NI so it's just a duplication (ballast) and you won't want to use your own interfacing with the eclipse enviornment. But they really offer the most advanced uml support I've seen in any of the free toolkits. Felix Quote Link to comment
Black Pearl Posted May 8, 2010 Author Report Share Posted May 8, 2010 Daklu, thanks a lot for the bag implementation. I was trying to avoid this topic and move towards having a real 'uml_class' object. But properties inherit (via structuralFeature) from multiplicityElement. To make things a bit more difficult, multiplicityElement has two attributes: isUnique and isOrdered. So I propably need to cover all 4 cases. I think I'll try to make it a List (they call that Sequence in the uml specs). Later I can enforce the uniqueness if isUnique. And I can introduce isOrdered=false later as well. Felix Quote Link to comment
Daklu Posted May 10, 2010 Report Share Posted May 10, 2010 So they are not two flavours of implementing the same by-ref. But they are two different use cases: make a class by-ref or use an object by-ref. Yep. At least that's the way I use it. Daklu, thanks a lot for the bag implementation. No problem. Glad you found it useful. I was trying to avoid this topic and move towards having a real 'uml_class' object. But properties inherit (via structuralFeature) from multiplicityElement. To make things a bit more difficult, multiplicityElement has two attributes: isUnique and isOrdered. So I propably need to cover all 4 cases. Are you trying to create something that will generate code from a uml diagram, or just a uml diagramming tool for Labview? (Either way you certainly aren't shy about jumping into the deep end of the pool when learning how to swim.) Quote Link to comment
Black Pearl Posted May 11, 2010 Author Report Share Posted May 11, 2010 Are you trying to create something that will generate code from a uml diagram, or just a uml diagramming tool for Labview? (Either way you certainly aren't shy about jumping into the deep end of the pool when learning how to swim.) I aim for a code generation tool, I'm addicted to 'scripting'. But I enjoy the privilege of having this as a private project, so I don't expect any tool in the near future. I fear that a major work far on the horizon will be to get uml adapted to LVOOP specific concepts. There is the redefinition package in the uml framework, but I havn't looked at it. I did OOP years ago in JAVA, so the basic OOP concepts are not unfamiliar, neither are some of the GoF design patterns. It is a lot of new concepts though, digging through the 100s of pages of uml specs, LVOOP threads on LAVA, scripting and hooks on LVOOP, the eclipse source code... But so far I can manage to break things down into smaller issues, as this is my everyday engineering task. I'm heading of for holidays this afternoon and might be back on this the end of next week. Felix Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.