Gist of the Day: There's More Than One Way to Switch Your Class!

In programming you will commonly see abstraction layers. Say, for instance, you wanted to have a program to take some arbitrary data format and load it into your data. We’ll call it product feeds (just because some people like relevant examples). So, you want to pull in product data from various different web sites and vendors to list some products on your site. Now if you have ever done this task before then you’re already thinking “jeez, what a pain in the ass it is to get everybody to use the same format!” If you’ve done this task before then you’ve probably already dabbled in this type of abstraction. In Perl – my go-to language (since I arbitrarily prefer it) – this usually results in dynamically loading a “driver” class based on the data format which is likely vendor-specific. This makes it easy/easier for you to allow each data source to have its own format while using them all in the same way.
If you were to pick a Design Pattern for this, you would do well to choose a Factory pattern. In Perl, since you have so much dynamic leeway, you don’t need to go with a pure Factory pattern here, but what you end up with I think is most certainly in keeping with a factory pattern.
For this Gist, I will demonstrate three common ways to get from knowing which class you need to getting that class:

For the purpose of this demo we’re going to use animals, because arbitrary samples are much quicker than more functional ones. We’ll have an Animal base/factory class, and then we’ll have a Seahorse subclass and a Chicken subclass. The two subclasses will each implement the common method respirate(), but each will implement it differently. The Seahorse subclass will implement the swim() method, and the Chicken subclass will implement the method fly() (yes, I know chickens don’t fly very well, but they can technically fly).
From there I will demonstrate the three common ways to get an instance of each, and then write a quick test (using Test::More as usual) to prove that they work.
For the demo I am using my Gist here: https://gist.github.com/manchicken/6661809
For the full code demo, see here: https://github.com/manchicken/gist_per_day/tree/master/Perl/AbstractDemo

The Classes Involved

Here are the basic classes we’re going to use here…

 Animal.pm

This is the Factory class, essentially. I’m not using inheritance here (because it doesn’t add to the demo), so it’s not technically a base class. This class essentially allows you to create an instance, and then to morph the instance into either a Chicken or a Seahorse.

Chicken.pm

This class implements the respirate() method and the fly() method.

Seahorse.pm

This class implements the respirate() and the swim() method.

Greater Detail

As with most things in programming, there are trade-offs to each of these techniques, and you really should know them. You also should know the details of what

Use and re-bless

This technique is very close to a Factory pattern, but it isn’t. With this method you essentially change the class on a live instance. There really aren’t an awful lot of side-effects of this, but you do need to make sure you’re controlling which classes you’re supporting mainly so that you can avoid someone trying to rebless to a non-existent package.

Module::Runtime

This is most likely to be considered the best practice. Since this module does pretty much all you need (through using require()) you are unlikely to need more than this.
The biggest side-effect of this loading technique is that it uses require() and never calls import(). If the module you’re using depends on a call to import() then you might want to try using require_module() defined in this module, and them manually import(), or use another mechanism for loading.

Eval of use and instantiation

This is the riskiest method, and I will probably catch flack for mentioning it. The #1 risk here is that you’re taking information provided to your function and you’re eval()’ing it. That’s a huge security problem as it could open your program up to arbitrary code execution. There are ways to mitigate this one, usually through string comparison or use of regular expressions, but you need to be super careful. I usually just run a regex to make sure that the value has no characters other than digits, letters, colons, hyphens, and underscores.
This is a valid method of loading a module, but it is the least likely to be appropriate. You probably only want to run this if you have something that needs to happen at compile time, or if you’re using modules from a black box.

The Demo Code


Here you can see how I’m using each of the three techniques, and I’m proving how after each of them I get instances of the proper class. Then I make sure that a Chicken can respirate() and fly() but not swim(), and a Seahorse can respirate() and swim(), but not fly().

The Conclusion

There are many ways to perform this task, these are just three. I’ve found this to be a very useful way to tie a bunch of systems which do the same thing but in different ways together.
I hope you like this, and if you want to share your favorite way to solve this problem feel free to do so in the comments section. If you’re going to tell me that I’m going to burn in hell for sharing the string eval method, I know. When commenting please remember Wheaton’s law.
 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.