Mirash Posted May 4, 2011 Report Share Posted May 4, 2011 Hello, Recently i got introduced to LVOOP and I decided to create simple data acquisition application using OOP concepts. What I am planning to do is a Strain and Vibration Data Acquisition System. So for Configuration part, I created a Generic class for configuration and planning to inerit two child classes for Strain and Vibration configuration. I try to draw a conceptual UML diagram for this (Please excuse, I am not good at UML ) My confusion is , I have Strain Configuration cluster in "Strain Configuration" class and Vibration Configuration cluster in " Vibration Configuration " Class. But inorder to pass these into a Data Acquisition module, I need a master cluster with both Strain and Vibration configuration cluster. Where should I put this master cluster? Should this master cluster be part of the Generic Configuration Class? Normally I use an LV2G for holding the configuration and update the corresponding cluster when I read the configuration. Is that the same way I should follow here also? Sorry for puting lots of queries. But I am kind of stuck here. I want to use the proper method while using classes. Any help towards this will be greatly appreciated. Thank you.. Mirash Quote Link to comment
dpga Posted May 4, 2011 Report Share Posted May 4, 2011 Others might have better advice, but I'd rearrange your objects. The parent could be a generic object of a measurement to be made. The children would be strain and vibration. Methods under strain and vibration would include configuration and acquire. Good luck.. Quote Link to comment
PaulL Posted May 4, 2011 Report Share Posted May 4, 2011 The way I do this is to use composition. That is, make the configuration elements attributes in the class that needs them. (You can use the same configuration in multiple classes.) In the image, then, I have added the configuration clusters as attributes in the DataAcquisition class. Notes: I have made these clusters, instead of classes, since in practice I just need to read and write these from a file and I have developed a library to do this, so I don't need to define any methods on the configuration items themselves. (Using clusters means I don't have to use accessor methods, for instance. These are just data.) I define an init method on the class that needs the data (could be part of the DataAcquisition:configureChannels method, in your case, preferably a separate method, though, even if private) that calls the common method to read the files and casts the results to the cluster types. You could opt to use classes instead of clusters for the configuration items, though, if you wish (just change the definitions of the clusters to classes!). I initially defined a ConfigurationHandler class outside my main classes, and put the various configuration classes (they were classes at that time) in it (very similar to what you have done). The thought was that the configuration would only exist in one place. This works but it quickly prompted questions about the relationships with the model classes and never seemed to have a proper place. I eventually transitioned to the compositional approach I have described and I am much, much happier with it. I very much like my current approach. It is simple and repeatable, and data is where it needs to be. Quote Link to comment
Daklu Posted May 5, 2011 Report Share Posted May 5, 2011 Where should I put this master cluster? Should this master cluster be part of the Generic Configuration Class? We can't answer that question. There are many solutions that work, each with advantages and disadvantages. The "best" solution depends on what your exact goals are. dpga's suggestion make the measurement instead of the data acquisition device a principal object in your application. Advantage - it might be easier to replace the hardware at a future date. Disadvantage - taking multiple measurements from the same device could be a little more confusing. Paul's suggestion works too. Advantages - simple to understand and fast to implement. Disadvantages - it couples the config file (and schema) to the daq object, which may make it harder to add flexibility later on. If you need to decouple the config info from the daq device in source code (meaning neither class is dependent on the other) then you should define an interface for the config class that returns all the config info using only native Labview types. One way I've done this in the past is by adding a Config:GetConfiguration method that returns a 2d string array containing keys and values for every config option. You could pass that into a Daq:SetConfig method that looks up the values for the keys it's interested in. Advantages - no dependencies, making it easier to reuse or vary either class independently. Disadvantages - overkill if reuse and flexibility are not primary goals. Personally, I try to make sure none of my class method parameters are clusters. Using clusters that way usually results in bad code smells for me. Quote Link to comment
PaulL Posted May 5, 2011 Report Share Posted May 5, 2011 Paul's suggestion works too. Advantages - simple to understand and fast to implement. Disadvantages - it couples the config file (and schema) to the daq object, which may make it harder to add flexibility later on. I think of configuration data as attributes for the class that uses them, with the added constraint that we read them from a file. Hence I need a reference to the type (but I already need that since it is in the class control definition) and I need to call two common methods (retrieveCfg, that I developed), and Unflatten from XML (core LabVIEW) in the class init method: Since the configuration is just (usually complex) data (no methods), i.e., a custom data type, I don't mind not making it a class [and it is practically simpler to access the data and in some cases necessary to satisfy loop speed requirements if it is a cluster), although a strict type-defed cluster doesn't have the version-safe behavior that a LabVIEW class does, as you have elsewhere noted]. (By the way, what historically drove me to use typedefed clusters instead of classes was that I needed to display the information on a configuration editor's View, and currently classes don't have a control I can put on a View (OK, and I think that using "XControls" as "Friends" is not a practical solution). I think it is certainly fine to define configuration classes, but for the reasons indicated I think it is simpler to use essentially custom data types.) Personally, I try to make sure none of my class method parameters are clusters. Using clusters that way usually results in bad code smells for me. OK, I'm curious. Why not? (Is it because of the version to version update issue or something else?) I haven't (yet) found a compelling reason to avoid using strict typedefs of data (used strictly as data) within an application. (In practice, I don't do it often, but I don't have a rule against it.) By the way, where I do use typedefs a fair amount apart from configuration definitions is not for clusters, but for Enums. While I do recognize that classes will update correctly from version to version, my design approach means that this has never been an issue for me with strict typedefs either. By the way, I'm obviously not arguing against the use of LabVIEW classes. I haven't written a non-test or top-level VI that isn't in a class (well, maybe a couple for very special purposes) for quite some time now! :-) And, OK, in fairness, I'm using my own custom XML-based configuration editor, not "ini" files requiring a lot of customization for each data type. Of course, I think the simpler XML approach is the way it should be! :-) Lol Quote Link to comment
Daklu Posted May 6, 2011 Report Share Posted May 6, 2011 I think of configuration data as attributes for the class that uses them, with the added constraint that we read them from a file. Yeah, that's what I was thinking about when I responded. I'm not questioning that it's a good solution given your requirements and use case, it just make certain kinds of functional extension a little more difficult. For example, maybe the configuration parameters need to be adjustable by the end user at runtime through a dialog box or something. Ideally that could be implemented without changing the DataAcquisition class since nothing about the data acquisition instrument or data collection has changed. This gets back to the separation of concerns and the idea that a class should only have one reason to change. OK, I'm curious. Why not? (Is it because of the version to version update issue or something else?) The revision update issue is one reason. Using a cluster on the conpane also significantly limits my ability to meaningfully override the method in a child class. But I have also found that when I use clusters as method parameters I tend to design my code for specific situations instead of thinking about it in more general terms. I end up with poorly constructed code and confusing class apis. Sometimes I've constructed classes similar to superclusters where only part of the data has any meaning. And, OK, in fairness, I'm using my own custom XML-based configuration editor, not "ini" files requiring a lot of customization for each data type. That could be a big reason it is working out so well for you. Quote Link to comment
MikaelH Posted May 6, 2011 Report Share Posted May 6, 2011 Welcome to LAVA Mirash I did a quick uml design and code example for you to have a look at. Hope it helps, this is just one way of solving the problem and if it's a small system it might be overkill. DaqProject.zip Cheers, Mikael Quote Link to comment
PaulL Posted May 6, 2011 Report Share Posted May 6, 2011 For example, maybe the configuration parameters need to be adjustable by the end user at runtime through a dialog box or something. Ideally that could be implemented without changing the DataAcquisition class since nothing about the data acquisition instrument or data collection has changed. Certainly. We accomplish this with a configuration editor that is completely independent of the model (e.g., DataAcquisition class). It does need a reference to the configuration typedef. (We collect the configuration typedefs in a library.) We can add any functionality we want to the configuration editor without ever touching the DataAcquisition class. The revision update issue is one reason. Using a cluster on the conpane also significantly limits my ability to meaningfully override the method in a child class. I was actually thinking about this thread during a long drive down to Phoenix yesterday, and anticipated the need to generalize the data as a possible objection. It is certainly true that I can't override a cluster, but since the use case is to handle a definition (really a collection) of a very specific set of flat-by-definition configuration data, in practice I have no need to do this. (We have other types of essentially data classes--e.g.,commands, where inheritance is necessary, so classes are necessary--well, command classes have methods, too, so clusters won't do. To date I have not had a reason to inherit a configuration definition, though of course it may make sense to do this given another paradigm.) Why does using a cluster significantly limit the ability to override the method in a child class? I don't see why that is. Maybe this is another aspect of version control considerations? I honestly can't think of a serious problem here.... Quote Link to comment
Daklu Posted May 6, 2011 Report Share Posted May 6, 2011 Certainly. We accomplish this with a configuration editor that is completely independent of the model (e.g., DataAcquisition class). Yep, which is one of the reasons why the solution works for you. To reiterate and make absolutely clear to anyone bothering to read this, I'm not claiming your solution is wrong or inferior to any other solution. I'm not claiming there are insurmountable obstacles that can't be resolved with your solution. I'm just pointing out potential consequences of the various designs to the OP, who as a Labview user just starting in LVOOP, probably doesn't have any idea how much these early decisions can affect future changes. (I know I didn't.) Why does using a cluster significantly limit the ability to override the method in a child class? It significantly limits my ability to meaningfully override the method in a child class, because the custom type defined by the parent class' method often does not match the custom type I need for the child class' method. For example, let's say I make a Clock class with a SetTime method. To make it "easy" for end users I gave the SetTime method an input cluster with 3 integers representing hour, minute, and second, and an am/pm enum. Then suppose for some reason I need to create a MilitaryClock child class that only publically supports 24 hr time representation. What are my options? -Assuming I have access to Clock's source code I could edit the am/pm enum and add a third option, "24hr." Except now I have a parent class method for which only two of the three options are valid and a child class method for which only one of the three options is valid. Why would a MilitaryClock.SetTime method, which handles time representations in 24 hr format, have an option to enter it in 12 hr format? -Another option is to create a new method in the child class, Set24HrTime, and duplicate the input cluster without the am/pm enum. This makes that particular method call more straightforward, but the overall api isn't much better. The end user still has the option to call Clock:SetTime (using 12hr format) on a MilitaryClock object, which according to the business rules shouldn't be possible. Now I'm stuck overriding the Clock:SetTime method and throwing an error, which enforces the business rules but makes the api even uglier. Neither option is very satisfying. I don't think there is a really clean way to implement the MilitaryClock class without eliminating the cluster from the parent's SetTime method. That's the kind of stuff I meant when I said I often end up with poorly constructed code when I use clusters as part of a class' public api. Clusters are a user-defined type, and when I use them I tend to define them for the specific use case I am working on at that moment... you know, so they're "easier" to use. (Again, your approach isn't wrong, it just contains a different set of tradeoffs.) ------- By the way, in your Compensator.init method above you have errors generated within that vi overwriting errors coming in on the input terminal. Is there a specific reason you do that? I usually prioritize all input errors over errors generated within the vi itself (or within its sub vis) simply because the current vi might be failing because of an error that occurred previously, and I want to know about the first error, not the last error. Of course, this vi doesn't have any other inputs so it doesn't really matter that much, but I was just curious... Quote Link to comment
PaulL Posted May 7, 2011 Report Share Posted May 7, 2011 Yep, which is one of the reasons why the solution works for you. To reiterate and make absolutely clear to anyone bothering to read this, I'm not claiming your solution is wrong or inferior to any other solution. I'm not claiming there are insurmountable obstacles that can't be resolved with your solution. I'm just pointing out potential consequences of the various designs to the OP, who as a Labview user just starting in LVOOP, probably doesn't have any idea how much these early decisions can affect future changes. (I know I didn't.) Fair enough! :-) It significantly limits my ability to meaningfully override the method in a child class, because the custom type defined by the parent class' method often does not match the custom type I need for the child class' method. I see your point. I guess I see this is as a matter of asking at design time what might change. I can see how it is possible to paint oneself in a corner using a cluster input, and so it would make sense to keep the data out of clusters. On the other hand, if one is just using this data in multiple classes within a single application, I think using strict typedefed clusters is not a bad option. I now see what you mean, though. By the way, in your Compensator.init method above you have errors generated within that vi overwriting errors coming in on the input terminal. Is there a specific reason you do that? I usually prioritize all input errors over errors generated within the vi itself (or within its sub vis) simply because the current vi might be failing because of an error that occurred previously, and I want to know about the first error, not the last error. Of course, this vi doesn't have any other inputs so it doesn't really matter that much, but I was just curious... Short answer: It is a bug on my part. I usually do wire the input to the top terminal of the Merge Errors function, but I haven't been exceptionally careful about it because I dÃdn't know the Merge Errors function cared (but it makes sense, of course, that it does). Now I know I must wire the terminals in the correct order. Thanks for catching this and pointing it out to me! Quote Link to comment
Yair Posted May 9, 2011 Report Share Posted May 9, 2011 I don't think there is a really clean way to implement the MilitaryClock class without eliminating the cluster from the parent's SetTime method. This obviously isn't a fair question, but what method would you actually be likely to use (use number of seconds in day, create a helper time object, parse a string, have separate inputs for T/M/S, etc.) and why? Quote Link to comment
PaulL Posted May 9, 2011 Report Share Posted May 9, 2011 This obviously isn't a fair question, but what method would you actually be likely to use (use number of seconds in day, create a helper time object, parse a string, have separate inputs for T/M/S, etc.) and why? Jumping in: I thought about this and I would suggest making an adapter (which one would need any time one redefined an input type, not just if it's a cluster). Paul Quote Link to comment
Daklu Posted May 10, 2011 Report Share Posted May 10, 2011 This obviously isn't a fair question, but what method would you actually be likely to use (use number of seconds in day, create a helper time object, parse a string, have separate inputs for T/M/S, etc.) and why? Good question. (And very fair, imo.) Jumping in: I thought about this and I would suggest making an adapter (which one would need any time one redefined an input type, not just if it's a cluster). In general I'd use an adapter if the Clock class were already created and modifying the code is unfeasable, either because I don't have access to the source or the class is part of a reuse library and I can't change the api. For the application I'm hypothetically working on using an adapter doesn't save me much. I still have to go modify the app source code everywhere the class is used and replace the Clock class with the adapter class. It's pretty much the same amount of effort to rework the Clock method parameters directly. But I think Yair's question refers to how I would have originally designed the class. It really depends on what role I expect the class to play and what the objects are interfacing with: the UI, an external database, internet time server, etc. (If the classes were entirely internal to my app it'd make more sense to just use LV's native timestamp and skip the child class.) Most likely if it were a UI class I'd use a string (assuming I didn't want to use the timestamp controls on the UI) and if it were a data interface class I'd use one of the integer or floating point standards based on the number of seconds since some date. Also, sometimes I'll give a class multiple create methods for different styles of possible inputs. So for a UI Clock class I might have Create Clock(String), Create Clock(Int1904), and things like that. The output would always be a string though, since the purpose of the class in this situation is to represent the time for the user interface. 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.