in reply to Factory classes in Perl

A more real-world case that I deal with fairly regularly is one of code portability across environments that have differing constraints. Lets consider the idea of a JSON parsing class. I know this is a solved problem, but for the purposes of illustration it may be useful.

I may have one class of servers where it is acceptable to assume that JSON::XS is available. I may have another class of servers where a recent enough Perl version is available that I can assume JSON::PP is available. So that means 5.14 or newer. And there's yet another class of servers where I cannot make any assumptions other than core Perl, and whatever code I explicitly bundle. In those environments, I have a lot of constraints that prevent me just sucking in whatever I want from CPAN (for example, tens of thousands of servers where a goal is to keep our footprint minimal).

But additionally, we have these concerns: Primary: We should use the most CPU-efficient JSON parser available. Secondary: We should use the most standardized JSON parser available. Tertiary: We should use a JSON parser that has no dependencies other than what we package and ship with our code. There is a module, JSON::Any, which handles this decision for us, but may not have the same prioritization, plus it is another dependency that I'm not guaranteed to have available in all environments. So here's a home-grown strategy to select the JSON module for us, based on the order that I might prefer for this example:

#!/usr/bin/env perl use strict; use warnings; use Data::Dumper; { my @JSON_MODULES = qw( JSON::XS JSON JSON::PP JSON::Tiny ); my $json_class; for my $module (@JSON_MODULES) { if (eval "require $module;") { $json_class = $module; last; } } die "No JSON parsing class found. $0 requires one of ", join(', ', @JSON_MODULES), ".\n" unless $json_class; warn "Using JSON module '$json_class'.\n"; $json_class->import(qw(encode_json decode_json)); } my $json = '{"foo":"bar", "biff":["baz","buzz"]}'; print "\nData structure parsed from JSON:\n", Dumper(decode_json($json +));

If I run that on a system that has JSON::XS installed, I get this output:

Using JSON module 'JSON::XS'. Data structure parsed from JSON: $VAR1 = { 'foo' => 'bar', 'biff' => [ 'baz', 'buzz' ] };

If I run it in an environment that has neither JSON::XS nor JSON, but is Perl 5.14 or newer, I'll get this:

Using JSON module 'JSON::PP'.

If I run it in an environment that has no non-core modules, and only what I bundle with the code I'm shipping, I better be sure to ship JSON::Tiny, and if I do as I should, the output would be:

Using JSON module 'JSON::Tiny'.

...followed by my data structure dump. The point being that now I've provided flexibility that places a low burden for what modules must be packaged or available. Sure, for many environments I can just pull something from CPAN. For some, I need to have the CPAN distribution built out as an RPM, and for some, keeping the dependencies down to only what ships with the code itself may be best.

This example used a set of modules that don't require an object oriented interface, but if they did, that would be just as easy; rather than calling $module->import, I could call my $json_parser = $module->new, assuming they all implemented the same OO interface.

If you are referring specifically to the Factory pattern, the OO version I mentioned is closer to that pattern. But the Factory pattern has uses beyond only dealing with not knowing what module may be available in a given environment. The classic examples usually deal with subclasses that can't be decided at time of writing the code, but that must share a common interface with some base class, pure virtual base class, or interface role. For example, a driver needs to be able to $car->refuel, but whether the car is an instance of a Ford Van class, or a Honda Sedan class is handled by the factory.


Dave

Replies are listed 'Best First'.
Re^2: Factory classes in Perl
by hippo (Chancellor) on Jan 09, 2021 at 11:36 UTC