Jump to content

Object composition woes


Recommended Posts

I admit it. Long ago I drank the kool-aid AQ was offering and I liked it. Despite all the bumps and bruises I've come back for a second serving by jumping Head First into Design Patterns. One of the bigger questions I haven't answered is how to best implement design patterns that use abstract classes when composing objects. For example, in my collection package the Variant Collection class has a CollectionImp object as private data with the expectation that the actual object at runtime will be a BufferedArray, HashTable, or LinkedList.

post-7603-125077698628_thumb.png

This presents problems in the Labview environment. Dropping a Variant Collection object on a VI creates a Variant Collection with a CollectionImp object instead of one of the child class objects. Since the CollectionImp class has no code implemented in its methods the Variant Collection object will not work. Unfortunately Labview currently has no way to enforce initializing an object's private data at runtime.

One common workaround is to have the abstract methods in CollectionImp throw an error at runtime. This works but it doesn't prevent the mistake in the first place and I am really tired of writing errors in all my abstract methods. (On top of that naming becomes an issue. How many ways can I say "raise error?") I was very excited to see the Must Implement option in LV2009. Initially I thought I wouldn't have to write any more "Invalid call to abstract method" errors. It turns out my excitement was short-lived.

Another suggested workaround is to make the Variant Collection class a private member of a wrapper lvlib. This prevents users from dropping an object directly on a VI. Then you add a factory vi to create Variant Collection objects with the appropriate CollectionImp child class and wrapper VIs for each Variant Collection method to be exposed. (I've attached a modified version of Christina's Factory Pattern example that illustrates this.)

This has several consequences. First, it's a lot of extra code to write, icons to make, documentation to verify, etc. Second, wrapping everything in a library means all of it will be loaded into memory rather than just the implementations that are used. Third, package users cannot derive their own collection implementations from the abstract class. Whether this is a positive or negative depends on your specific goals; nevertheless, it is a consequence.

A third workaround is to replace the abstract CollectionImp class with a default concrete child class. (This is what Christina did in her Factory Pattern example. The "Generic Plugin" is in reality a "Black Plugin.") By doing this the problem associated with dropping a Variant Collection object directly on a vi goes away and a Create New vi is not needed. A mechanism needs to be provided that allows collections to use other implementations, but there is some sort of working default implementation.

post-7603-125077699667_thumb.png

My hesitancy to use this method is partly because it violates the design principle of Depend on abstractions rather than concrete classes. I still don't have a good idea of how that design principle applies to Labview or what the long term consequences of violating it are. It's an unknown and I have no way to evaluate the risks.

Another downside is that it can be harder to reuse the concrete implementation classes. The default concrete implementation may have methods that are not appropriate for the child implementations. For example, BufferedArray has Grow and Compress methods to allocate and recover array space. These may not have any practical meaning in the context of a hash table or linked list, so I'm forced to override them will null methods. This is generally considered bad OO design. If sometime in the future a developer were to use the HashTable object directly, they would find the class api littered with null methods.

So my fellow LOOPers, how do you handle this situation? Do you typically use one of the methods here or a mix of all three? Are there other ways of dealing with the lack of object constructors initializers that I haven't listed? As always, all comments are appreciated but I'm especially interested in hearing from those who have developed a reusable code base for other LV developers.

Wrapped Factory.zip

Link to comment
Since the CollectionImp class has no code implemented in its methods the Variant Collection object will not work.

First I'd like to offer a clarification. An abstract class may have one or more abstract methods, but it may contain nonabstract methods as well. If all the methods are abstract then it functions like a Java interface. (See p. 170 in An Introduction to Object-Oriented Programming, 3rd. ed., by Timothy Budd).

Of course LabVIEW OOP does not currently support the definition of abstract classes or interfaces, so anything we do here is on our own. (I agree that I would like NI to change this in the future.)

Currently the class hierarchies we use pretty much always include classes that we define within our project (via UML models) as abstract. This works well for us but only because we use the classes correctly based on agreed convention. It would indeed be problematic if we wanted to release the classes for general use by other developers.

Link to comment

> Dropping a Variant Collection object on a VI creates a Variant

> Collection with a CollectionImp object instead of one of the

> childclass objects.

Go to Variant Collection.lvclass:Variant Collection.ctl. Change the default value (not the type, just the value) of the CollectionImp control to be an instance of one of the concrete types -- HashTable, for example. Now whenever you drop VariantCollection.lvclass, you get one that has a hashtable unless/until you change it to something else.

  • Like 1
Link to comment

First I'd like to offer a clarification. An abstract class may have one or more abstract methods, but it may contain nonabstract methods as well.

Yes, but in the example I'm presenting all the methods are abstract.

If all the methods are abstract then it functions like a Java interface.

Not quite. Interfaces typically share some of the same behaviors as classes (such as inheritance) but an interface is a separate and distinct construct with unique abilities. Making all of a class' methods abstract does not give it the abilities of an interface.

Currently the class hierarchies we use pretty much always include classes that we define within our project (via UML models) as abstract. This works well for us but only because we use the classes correctly based on agreed convention. It would indeed be problematic if we wanted to release the classes for general use by other developers.

I take this to mean you typically have factory VIs or some other method to manually insert the child object (HashTable, etc.) into the client object (Variant Collection)?

Change the default value (not the type, just the value)...

At first I thought you were describing the third workaround I mentioned. Then I reread this section and remembered once long ago when I accidentally changed the default value of an object cube and ended up with a black box around it. I'm guessing you're referring to doing the same thing here? I went ahead and followed your instructions from that thread and now have the lovely black box around the CollectionImp object in my VariantCollection.ctl. This does seem to solve the concerns I have with the other three methods I mentioned. As an added bonus, it also answers my use case question in the other thread! I do wish there were a better way to set an object to non-default values and determine what the actual object is though.

Link to comment

So my fellow LOOPers, how do you handle this situation?

Hi

For normal/simple implementations I just tell the "public" class what type of objects it should aggregate/create as an input to the constructor, and have a case structure inside like this:

post-941-125080688637_thumb.png

And for this design, I normally store the TestInstrument objects in a class attribute belonging to the TestInstrument class.

And with a static method "TestInstrument:GetObjectByName", I can retirve the instrument object.

But when I develop TestSystem Framwork I use the Factory design pattern.

Cheers,

Mikael

Link to comment

Join the conversation

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

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

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

×   Your previous content has been restored.   Clear editor

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

×
×
  • Create New...

Important Information

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