ensegre Posted November 10, 2014 Report Share Posted November 10, 2014 I'm just dipping my feet in LVOOP, and hit a wall. I thought of a design -- possibly bad -- in which I maintain a list of devices as an array of instances of a few possible different lvclasses. To represent the devices as lvclasses with a hierarchy seemed me a good start -- all the devices have common parameters, and particular individual properties, for which I created children classes. I can have generic accessors for the common parameters, and children class-specific ones for the particular. It seemed also proper to me, to have override VIs to cause each particular piece of hardware perform a certain generic task in its own way. As for storing a collection, arrays obviously have the advantage of being growable and accessible by index, where elements can easily interchanged, whereas I couldn't think a way of doing the same e.g. with clusters. But if I group heterogeneous devices in an array, the array becomes of the ancestor class, and I lose the access to peculiar properties. Ditto if for instance I wire different class constants to the same output tunnel of a case structure, and rightly so. I thought of circumventing the problem by making the array an array of variants (and being responsible for filling it only with the right objects). I can easily cast my objects ToVariant, but I'm at odd about how to retrieve them back for fiddling with the details. I browsed fora, I discovered vilib/Utility/VariantType.lvlib/GetLVClassInfo.vi, I can retrieve my class names from the variants, but I miss a way to manipulate their contents before recasting them to variant array elements. There are VIs on the Cluster&Class palette like "Get LV Class Default Value By Name", but I end up not knowing what to do with a most generic "Object" with no internal structure. I thought I could use the class name to access its .ctl via its path, but that would only bring me in a cumbersome way to the private data. Is there some better way? Quote Link to comment
chris754 Posted November 10, 2014 Report Share Posted November 10, 2014 (edited) You're missing something, but its tough to tell what you are doing wrong without code. Are you calling the parent class override method? You should be. The parent class override method will automatically call the correct child class method. Do you have dynamic dispatch enabled on the override methods? This should be as well. Using an array of Parent class is fine, no need for an array of variants or anything like that. Edited November 10, 2014 by chris754 Quote Link to comment
ensegre Posted November 10, 2014 Author Report Share Posted November 10, 2014 (edited) You're missing something, ... Quite likely. This should exemplify part of my problem. It's clear I can't wire a parent object to a child accessor directly, and I imagine that I could write a generic accessor for the parent, to be overridden by the children ones. But my children have many individual different parameters, and I hoped to be able to avoid writing a huge number of do-nothing accessors for the parent, covering the joint set of all parameters of all devices. I figured out that if I would be able to determine the child class at runtime, I could properly cast the wire. In fact I meant to go further for an ambitious GUI, to pop up a different control panel for each element of the device array selected, perhaps using dynamically dispatched FPs, but first things first. Thanks, Enrico Edited November 10, 2014 by ensegre Quote Link to comment
eberaud Posted November 10, 2014 Report Share Posted November 10, 2014 I wouldn't cast your objects into variant. The fact that you can have an array of the ancestor class is the whole point and beauty of OOP. What is important is how you create the object. As long as you create an object as a child class, the child overriding method - if there is one - will be called instead of the parent one when the method is called on one element of your array. For this to happen, you need dynamic dispatch of course. I put together a quick demo of what I'm talking about. LVOOP demo.zip Quote Link to comment
chris754 Posted November 10, 2014 Report Share Posted November 10, 2014 Yes, what Manu said. No need for casting, just call the parent method directly. The parent will choose the appropriate child method with dynamic dispatch. Exactly how the accessor method is working in your above example. Quote Link to comment
ensegre Posted November 11, 2014 Author Report Share Posted November 11, 2014 yes, almost. While I realize that casting all my children to an array of variants, as I initially thought, is an unnecessary and fragile detour, there is still a problem: each child has a different set of parameters. I can eventually cluster them and typedef them for convenience, but IIUC accessors and their overriders need the very same connector for parent and children. Thus I cannot just create a generic accessor "Parameters" for the top class, and pass a different argument to it for each children case, unless either a) I make that argument a variant (with the hassle of augmenting the caller with the logic for casting it back to parameters on read), or b) I create a set of do-nothing accessors for each possible set of parameters in the top class, and their individual overriders in each of the children classes. I'm currently seeing how far I get with b). In any case it seems that the caller will need some amount of casing "if children of type A, then call this accessor for A.parameters; if of type B, this other, etc." I have the vague impression that this obfuscates the beauty of OO, so maybe my design misconception. Quote Link to comment
candidus Posted November 11, 2014 Report Share Posted November 11, 2014 a) I make that argument a variant (with the hassle of augmenting the caller with the logic for casting it back to parameters on read), or b) I create a set of do-nothing accessors for each possible set of parameters in the top class, and their individual overriders in each of the children classes. I went with option A for one of my projects and I'm still happy with it (admittedly the class hierarchy was not big). Here is another idea: Maybe you even don't need parameter accessors. Make the argument a class that you use as a base for your parameters, let's say Parameter.lvclass. This class has a dynamic dispatch VI, Configure.vi. Derive classes from Parameter.lvclass, overriding Configure.vi. These different implementations of Configure.vi encapsulate the parameter access. Quote Link to comment
chris754 Posted November 11, 2014 Report Share Posted November 11, 2014 (edited) If you have a Parent class P, with child class A and B. If A has additional parameters that both P and B do not have that is fine. In this case, you know specifically which class you need, so then you would cast the object to class A and call the parameter for this specific purpose. If the parameter is in both P and A (and therefore B as well), no need to cast. The only time you cast is when the child class has extra information, that the higher level class does not have. I would stay away from variants for this case, it is an unnecessary complexity. Do-nothing accessors would also not be a good choice, as it would confuse the issue, and give class B irrelevant information. Edited November 11, 2014 by chris754 Quote Link to comment
drjdpowell Posted November 11, 2014 Report Share Posted November 11, 2014 But if I group heterogeneous devices in an array, the array becomes of the ancestor class, and I lose the access to peculiar properties. I think your conceptual problem is here. Your high-level, handles-array-of-generic-devices, code shouldn’t want to know anything about specific device properties. One of the biggest advantages of OOP is the ability to have generic code that doesn’t need to be complicated by a 1001 specific details. Anything specific needs to be pushed down a level to the specific child classes. The high-level code may have dynamically dispatched methods such as “Get Device configuration as Stringâ€, “Set Device configuration as Stringâ€, “Get Human-readable Device Statusâ€, “Show Device Configuration UIâ€, and so on. It should never have anything as specific as “Set Rate Formulaâ€. Now if you do need to write specific code that “knows†what the devices specifically are, then use a cluster, not an array (note that you can use a cluster of arrays of specific types if you have a variable number of each device type). Quote Link to comment
shoneill Posted November 11, 2014 Report Share Posted November 11, 2014 (edited) Your methodology in the picture you have provided is flawed. You write that the type of your object is only known at run-time yet you want to set a very specific parameter which is not present in the parent class. These two options cannot coexist without making some choices. 1) The object you want to work on is of type "Single Filament" or one of its children. 2) You only want to write the parameter IF the object of of type "Single Filament" or one of its children. I don't see how you can have both at the same time. If you NEED an object of the type "Single Filament" then you need to make sure of this at the point where you read it from the array. The function "To more specific" has an error out which can let you know if a required type is compatible with what is currently on the wire or not. Use this to your advantage. Either write a routine to retrieve the first element in an array of a given type or do the cast to more specific BEFORE you want access your parameters as in the lower portion of your picture. The code then operating on the parameters can go about its business in confidence knowing that the object is the right type because the cast was successful. Note that such a cast does NOT change your actual object in any way. It's not an expensive check. Shane I see some other posts have been made. To further explain: Writing a piece of code to extract ALL objects from an array which adhere to a specific type (i.e. the objects are all of Type X or it's children or grandchildren etc.) is a perfectly legitimate way of doing things. It's important to be prepared for the case where zero objects of that type are present. I do this as follows: Define a method in your class hierarchy called "Extract" and make it Dynamic dispatch. Pass to it a parent object and do your checking in the method against the current type (DD terminal should suffice). All the objects which do not throw an error on calling "Preserve Run-Time Class" are compatible with that object type. It can then output the newly tested object which is now of the correct type. On your BD (outside your class boundary) drop a constant of the types of objects you require from the array (this constant simply defines the DD type), wire it up to an instance of the method you just created and pass the parent object array to it via a for loop with conditional indexing and there you have your fixed type array. Edited November 11, 2014 by shoneill Quote Link to comment
ensegre Posted November 11, 2014 Author Report Share Posted November 11, 2014 Accumulating ideas while doing. I went with option A for one of my projects and I'm still happy with it (admittedly the class hierarchy was not big). Here is another idea: Maybe you even don't need parameter accessors. Make the argument a class that you use as a base for your parameters, let's say Parameter.lvclass. This class has a dynamic dispatch VI, Configure.vi. Derive classes from Parameter.lvclass, overriding Configure.vi. These different implementations of Configure.vi encapsulate the parameter access. I have now an implementation of my b), and start to think that now, having written enough typedefs, it would have been more compact a). Still experimenting, but as for yours, wouldn't that require that the hierarchy of Parameter.lvclass mirrors that of the device classes, hence on the long run more tedious to maintain? If you have a Parent class P, with child class A and B. If A has additional parameters that both P and B do not have that is fine. In this case, you know specifically which class you need, so then you would cast the object to class A and call the parameter for this specific purpose. If the parameter is in both P and A (and therefore B as well), no need to cast. The only time you cast is when the child class has extra information, that the higher level class does not have. I would stay away from variants for this case, it is an unnecessary complexity. Do-nothing accessors would also not be a good choice, as it would confuse the issue, and give class B irrelevant information. In my scenario A has its own parameters and B its own. In my present implementation the only do-nothing accessors are methods of P, like P.setParametersA, P.setParametersB. Then in A defines as override only A.setParametersA, doing the real thing. Not sure is yet the best option, but is a start. And do-nothing is really nothing, just a connector pane with the right number of inputs, where the parameter cluster is connected to nothing. Quote Link to comment
ensegre Posted November 11, 2014 Author Report Share Posted November 11, 2014 Your methodology in the picture you have provided is flawed. You write that the type of your object is only known at run-time yet you want to set a very specific parameter which is not present in the parent class. These two options cannot coexist without making some choices. 1) The object you want to work on is of type "Single Filament" or one of its children. 2) You only want to write the parameter IF the object of of type "Single Filament" or one of its children. I don't see how you can have both at the same time. in the picture I only meant to exemplify that one element of the array may be of type "Single Filament". My option is 2), I didn't get to write case selectors for that, sorry if I've been unclear. But sure if the caller BD would need case selectors, rather than selections being handled by the class hierarchy, design is questionable. Now I start to get a better picture. I think your conceptual problem is here. Your high-level, handles-array-of-generic-devices, code shouldn’t want to know anything about specific device properties. One of the biggest advantages of OOP is the ability to have generic code that doesn’t need to be complicated by a 1001 specific details. Anything specific needs to be pushed down a level to the specific child classes. The high-level code may have dynamically dispatched methods such as “Get Device configuration as Stringâ€, “Set Device configuration as Stringâ€, “Get Human-readable Device Statusâ€, “Show Device Configuration UIâ€, and so on. It should never have anything as specific as “Set Rate Formulaâ€. Now if you do need to write specific code that “knows†what the devices specifically are, then use a cluster, not an array (note that you can use a cluster of arrays of specific types if you have a variable number of each device type). Indeed, "Set Rate formula" was for an example, I'm going to “Show Device Configuration UIâ€. Now if you do need to write specific code that “knows†what the devices specifically are, then use a cluster, not an array (note that you can use a cluster of arrays of specific types if you have a variable number of each device type). In fact I need. My problems with clustering all children are that I don't know how to retrieve programmatically a cluster i-th element, save to replace it with an element of a different type (unless they are all variants), and that growing the cluster may be problematic; and with arrays of different device types, that each future device type would require a new array. Better to plan ahead. Quote Link to comment
drjdpowell Posted November 11, 2014 Report Share Posted November 11, 2014 Indeed, "Set Rate formula" was for an example, I'm going to “Show Device Configuration UIâ€. All Devices can implement “Show Device Configuration UIâ€, so you do not have any problem. In fact I need. My problems with clustering all children are that I don't know how to retrieve programmatically a cluster i-th element, save to replace it with an element of a different type (unless they are all variants), and that growing the cluster may be problematic; and with arrays of different device types, that each future device type would require a new array. Better to plan ahead. Your mixing two incompatible statements: you’re code is generic enough to switch types, but specific enough that it must use one specific type. If you organize your code in levels, generic and specific**, then each level will be a lot cleaner. **Note that you can have intermediate levels, such as a generic “power supplyâ€. Code can know that it is using power supplies (rather than generic devices) but not depend on the type of supply used. Quote Link to comment
ensegre Posted November 12, 2014 Author Report Share Posted November 12, 2014 (edited) Well. Again, my initial picture reflected my initial (mis)understanding about arrays of heterogeneous children, and was not yet a design intent. Certainly it will be a good exercise to cast neatly the hardware collection of this project to a properly structured hierarchy, and to keep things tidy. I dind't mention that my Cells are Devices as much as the Pyrometer or the Manipulator are, that all Cells have one Shutter and one or two Heaters, that also the Manipulator has a Heater, and, and... Anyway, summing up, I learned two things: 1) Arrays of heterogeneous children lvclasses may appear (if you probe them, e.g.) to be of parent class, but their elements retain their individual child type, with all it imples. This was surprising to me because you can't, in contrast, build an array of simple clusters collating elements of different cluster structure. 2) it is not pornographic to pass a variant to a generic accessor, and to let its override decide what to do. In fact, with proper typedefs, it looks to me so far the most economic, in terms of code effort, way of passing my parameters to my different cells. But I'm still learning. For the record, I attach a series pictures of what I'm trying to do. Some class structure: Planned use: Examples of a write and a read (child) parameter accessors Edited November 12, 2014 by ensegre Quote Link to comment
shoneill Posted November 12, 2014 Report Share Posted November 12, 2014 Why don't you include the VI for the configuration panel in the class definition. You can call a method of any given object o launch it's configuration panel (it will know which one to call) and then it can automatically pass back the correct data for the correct object type. Expanding the class boundaries to incorporate the UI precludes many of the problems you are currently encountering. Your object can maintain a reference to the control and read it out (strictly typed) as required. I also had trouble in the beginning understanding the difference between what the IDE told me was the type of a wire and what was REALLY on the wire. Using heterogenous collections like this, it's best to include as much of the object manipulation you can IN the object in question, UI included if possible. You're already most of the way there with subpanels. Quote Link to comment
ensegre Posted November 12, 2014 Author Report Share Posted November 12, 2014 Why don't you include the VI for the configuration panel in the class definition. You can call a method of any given object o launch it's configuration panel (it will know which one to call) and then it can automatically pass back the correct data for the correct object type. Expanding the class boundaries to incorporate the UI precludes many of the problems you are currently encountering. I sort of do, it's the CellParametersFrontPanel in the proj; I have one for each class. However, I want to keep the right one visible all the time in a subpanel of the main VI, rater than calling it once with a method. I therefore poll at intervals its output for changes, and live with that output being a variant. This is what I do for now, till I see a smarter way. Quote Link to comment
drjdpowell Posted November 12, 2014 Report Share Posted November 12, 2014 I therefore poll at intervals its output for changes, and live with that output being a variant. You can still push the details into the subClass. Your generic code only needs a "Show Configuration UI in subpanel" method and a "Poll Configuration UI" method. You'll be able to write vastly simpler code in the subClass itself (no Variants, no Open VI ref, no Property nodes). Quote Link to comment
shoneill Posted November 12, 2014 Report Share Posted November 12, 2014 Like drjpowell says, the idea is completely compatible with what you're trying to do. You're so close.... Quote Link to comment
Yair Posted November 12, 2014 Report Share Posted November 12, 2014 I haven't read all the replies closely, but if I understand what you want correctly, you might also wish to look at the examples here - http://forums.ni.com/t5/LabVIEW/An-experiment-in-creating-compositable-user-interfaces-for/m-p/1262378#U1262378 AQ's example constructs the UI programmatically. Mine assumes that there's a UI for each specific class and opens a reference to it. Note that both are nothing more than proofs of concept. If you move ahead with them, you may find all kinds of issues. Quote Link to comment
ensegre Posted November 12, 2014 Author Report Share Posted November 12, 2014 You're so close.... I've been scratching my head thinking "is it me who doesn't see it because I'm fresh to OO, or" for a while... But there may also be design reasons for me to keep things like this (and fit to my competence level) for the time being. Among them: Variants: a side advantage of the present form is that I can detect a class type change as well as a value change within the same type, just with a single disequality. bundling and unbundling: I'm fine with them for the time being, because I'm still figuring out which parameters of each class to expose as configuration, and which as operational. And for configurations things may get a bit tricky: i.e. Cells may have two Heaters, Heaters may have Configuration and Setpoints, but it is not said that Cell.Configuration is Cell.{Configuration.Heater1,Configuration.Heater2}; for some kind of Cells part of the second may depend on the first, and so on. VI refs: my anticipated scope is the only two calls above in a single main VI, I don't feel the need of abstracting for now. Also, I fear that both configurator examples mentioned by Yair are an overkill for my learning curve, but AQ's one I'd rule out since I don't want to configure the whole Cell class, only some of it. My design choice is to split classes per hardware, not per functionality. And I wouln't want to create a parallel hierarchy of Configurations. Quote Link to comment
shoneill Posted November 12, 2014 Report Share Posted November 12, 2014 I have to admit I don't understand your points fully. What disequality are you talking about? What kind of class type change do you want to detect? Again, I don't understand what you are bundling and unbundling in relation to the desired functionality I personally find launching a single clone per object and storing the reference within the object a much cleaner way of going about what you want to achieve. It also pretty much removes the possibility of having a mismatch between input parameter data and object type (possible related to your first point?) Each clone can be set up to display exactly the configuration data you require. I've done this as an experiment in the past and once you realise that that correctly defined boundaries greatly simplify how you can work with the objects it's a lesson you'll find hard to forget. Quote Link to comment
drjdpowell Posted November 12, 2014 Report Share Posted November 12, 2014 I've been scratching my head thinking "is it me who doesn't see it because I'm fresh to OO, or" for a while... But there may also be design reasons for me to keep things like this (and fit to my competence level) for the time being. It’s worth breaking through whatever conceptual block you have, because the code you have shown is considerably more complicated (and will require a higher level of competence to execute correctly) than the OOP solutions shoneil and I are talking about. And there are no advantages I can see to the way you are doing things. Quote Link to comment
ensegre Posted November 13, 2014 Author Report Share Posted November 13, 2014 Would this be better? I've moved the generic referencing to my config panels inside the children accessors (and duplicated it for every children), using class specific typedefs. Maybe it is more boundary savvy, still I found no better way to conceive my "read from class object and write to config panel" and "read config panel and write to class object" than as accessors for a VI ref. Accessors for parameters config would result in a different cluster for each class, with the previous issues; and the FP reference is needed in order to insert in the caller subpanel.The VI ref has to be generic in order to be overridden, hence I can't use a Call by reference, and still resort to retrieving the configuration panel control as a variant, and cast it within the WriteConfig accessor. In any event, if I'm asking too much attention on this thread, I can pause, and walk a bit on my own feet. Your assistance has been very helpful! Quote Link to comment
shoneill Posted November 13, 2014 Report Share Posted November 13, 2014 (edited) I, personally, would NOT use a VI reference inherited from the parent but would provide each and every class with its own reference which can then be strictly typed and does away with the variant altogether. I would also create a method in the parent class calles "Insert into subpanel" or something similar which takes a subpanel reference as an input and puts the appropriate VI in the subpanel. Extracting the reference is breaking encapsulation. Also, if the VI launched is actually part of the class, code from outside the class actually doesn't have permission to put it in a subpanel, or so I thought at least. I can only speak for myself but I'm really willing to invest time in getting people sorted with problems like this. It took me about 20 years between my last OOP class in college and actually finally understanding what was going on (it took LV 2009 to get me that far). As such I'm very much a n00b in this regard. But once the <click> has been made, it totally changes your perception. The <click> also can't be forced, it must come from within. Edited November 13, 2014 by shoneill 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.