http://www.perlmonks.org?node_id=552162

I have recently been working on a Perl/Tk app that I plan to host on Sourceforge for future development. As part of the preparation for this step, I asked a few people, with knowledge in the field this apps deals with, to review my approach and the usefulness of the app. And thats where my problems began ...

Firstly, not everyone I gave the app to had the most recent version of Tk installed, and they were not in a position to easily update the installed version (legacy apps bound to their old version of Tk, not their server etc.) And this was a problem for me, because I had developed against the most recent version (804.027), and not every widget I used in my app was supported in their older Tk install. For example, the Tk::Spinbox is not available in older Tk's. Also, some widget are available in all Tk releases, but some of the options those widgets support vary from release to release. For example, in the Tk::Listbox widget, the 'listvariable' option is not available in earlier Tk releases, requiring the user to manually populate the widget.

So, how can we support multiple versions of Tk (or any module with a long history and a varying interface) ? My solution involves

  1. creating a subroutine that does the right thing for each version of Tk e.g. one that creates Tk::Listbox's with the 'listvariable' option, and one that creates a Tk::Listbox without the 'listvariable' option
  2. making the 'right' subroutine available by a general name, once we know the version of Tk the user has installed.
  3. calling the function with the general name as required in the application.

In order to demonstrate these 3 steps with some code, lets imagine we have an application that needs to present some options for a user to select. For some options we want the user to select or enter a number, for others we want the user to select a value from a list. We have written this option display and processing code to all exist in one package/namespace we call Application::Option

package Application::Option; use strict; use warnings; use Tk; use version; ... # firstly, create subroutines that do the right thing for various Tk v +ersions # poulate Listboxes with modern Tk's sub renderListAutoPopulate { my (undef, $parentWidget, $listVariable) = @_; return $parentWidget->Listbox( -listvariable => $listVariable, # auto +populate from this list # other options as required )->pack( # pack options ); } # populate old Tk Listboxes sub renderListManualPopulate { my (undef, $parentWidget, $listVariable) = @_; my $thisListbox = $parentWidget->Listbox( # options as required )->pack( # pack options ); $thisListbox->insert('end', @$listVariable); return $thisListbox; } # present numeric options with modern Tk Spinbox sub renderNumericOptionAsSpinbox { my (undef, $parent, $value) = @_; $parent->Spinbox( # spinbox options as required )->pack( # pack options ); } # present numeric option as a Tk::Entry widget, for older Tk's sub renderNumericOptionAsEntry { my (undef, $parent, $value) = @_; $parent->Entry( # Entry options as required )->pack( # pack options ); } # secondly, populate typeglobs with the required function references # version.pm overloads relational and stringification operators conven +iently INIT { if (qv(Tk->VERSION()) >= "804.027") { # modern Tk *numericWidget = \&renderNumericOptionAsSpinbox; *listboxWidget = \&renderListAutoPopulate; } else { # older-style Tk *numericWidget = \&renderNumericOptionAsEntry; *listboxWidget = \&renderListManualList; } } # thirdly, call the code slot in the typeglob as required in the appli +cation ... } elsif (numericOption($option)) { $self->numericWidget($parent, $option); } elsif (listboxOption($option)) { $self->listboxWidget($parent, $option); } elsif (# other option types ...

It strikes me that this technique would also work well with other long-lived modules, like DBI, members of XML:: or Net::family etc. The key is to generalise what you want, then write specific subs to do that general thing for each version of the module you intend to support. I think that in Pattern language this is an example of an Adapter pattern.

...reality must take precedence over public relations, for nature cannot be fooled. - R P Feynmann