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:
- Use and re-bless
- Eval of use and instantiation
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…
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
[gist id=”6661809″ file=”Animal.pm”]
This class implements the
respirate() method and the
[gist id=”6661809″ file=”Chicken.pm”]
This class implements the
respirate() and the
[gist id=”6661809″ file=”Seahorse.pm”]
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.
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
[gist id=”6661809″ file=”demo.pl”]
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
fly() but not
swim(), and a
respirate() and swim(), but not
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.