In computer programming the extensibility pattern is one of design patternss that is to provide a framework for certain kind of tasks.
Problem: Supporting features, such as protocols, that don't yet exist. Solving general problems without concern for the specifics of details.
Solution:
Synopsis: Provide a framework certain kind of task.
Table of contents |
2 Configuration Files as Extentions 3 Extending Through Scripting 4 1 || $state |
A "framework" uses other modules.
Normal modules have a fixed set of dependencies and are only extended through subclassing, as per AboutInheritance.
A framework may consist of several parts that must be inherited to be used much like several cases of AbstractClass. It may also be passed references to other objects, as would a class that is sets up a ModelViewController. It may read names of classes from a ConfigFile or from the user, as in BeanPattern. Instead of code being used by other code, it will use other code on the fly. It is on top of the food chain instead of the bottom.
XXX examples of these cases as "extensibility".
A ConfigFile may be enough to customize the module for reasonable needs.
It may also specify modules by name to be created and employed in a framework.
require 'config.pl';
foreach my $listener (@listeners) {
require $listener;
my $list_inst = $listener->new();
$broadcaster->add_listener($list_inst);
}
A major complaint against GUIs is that they make it difficult to script
repetitive tasks. Command line interfaces are difficult for most humans to
work with. Neither give rich access to the API of a program. A well designed
program is a few lines of Perl in the main program that use a number of
modules - see CreatingCPANModules.
This makes it easier to reuse the program logic in other programs.
Complex programs that build upon existing parts benefit from this, without
question. How about the other case - a small script meant to automate
some task? This requires that the script have knowledge about the
structure of the application - it must know how to assemble the modules,
initialize them, and so on. It is forced to work with aspects of the API
that it almost certainly isn't concerned with. It must itself be the
framework.
This is a kind of AbstractionInversion - where something abstract is graphed
onto something concrete, or something simple is grafted onto the top of something
complex.
It would make more sense in this case for the application to implement a
sort of VisitorPattern, and allow itself to be passed whole, already
assembled, to another spat of code that knows how to perform specific
operations on it. This lends itself to the sequential nature of the
script: the user defined extention could be a series of simple calls:
# we are expected to have a "run_macro" method
sub run_macro {
my $this = shift;
my $app = shift;
$app->place_cursor(0, 0);
$app->set_color('white');
$app->draw_circle(radius=>1);
$app->set_color('red');
$app->draw_circle(radius=>2);
# and so on... make a little bull's eye
return 1;
}
Many applications will have users that want to do simple automation
without being bothered to learn even a little Perl (horrible but true!).
Some applications (like Mathematica, for instance) will provide
functionality that doesn't cleanly map to Perl. In this case, you'd want
to be able to parse expressions and minipulate them. In these cases,
a LittleLanguage may be just the thing.
XXX - move this to LittleLanguage.
A Little Language is a small programming language created specifically
for the task at hand. It can be similar to other languages. Having something
clean and simple specifically targeted at the problem can be better
solution than throwing an overpowered language at it. Just by neglecting
unneeded features, user confusion is reduced.
# 0 if we're expecting a function name, 1 if we're expecting an argument,
# 2 if we're expecting a comma to separate arguments
my $state = 0;
# perl code we're creating
my $perl = '
package UserExtention1;
sub run_macros {
my $this = shift;
my $app = shift;
';
while(1) {
# function call name
if($state == 0 && $input =~ m{\\G\\s*(\\w+)\\s*\\(}cgs) {
$perl .= ' $app->' . $1 . '(';
$state = 1;
# a=b style parameter
} elsif($state == 1 && $input =~ m{\\G\\s*(\\w+)\\s*=\\s*([\\w0-9]+)}cgs) {
$perl .= qq{$1=>'$2'};
$state = 2;
# simple parameter
} elsif($state == 1 && $input =~ m{\\G\\s*([\\w0-9]+)}cgs) {
$perl .= qq{'$1'};
$state = 2;
# comma to separate parameters
} elsif($state == 2 && $input =~ m{\\G\\s*,}cgs) {
$perl .= ', ';
$state = 1;
Every time we match something, we append a Perl-ized version of exactly
the same thing onto $perl. All of this is wrapped in a package and method
declaration. Finally, $perl is evaluated. The result of evaluating should
be to make this new package available to our code, ready to be called.
Beans as Extentions
Hacks as Extentions
When a base application, or shared code base, is customized in different directions for different clients.
Making heavy use of Template methods and abstract factories, localizing client specific code into
a module or tree of modules under a client-specific namespace rather than "where it belongs".
The article is originially from Perl Design Patterns BookFrameworks
Configuration Files as Extentions
# the config.pl file defines @listeners to contain a list of class names
# that should receive notices from an EventListener broadcaster,
# referenced by $broadcaster.
See EventListener for the broadcaster/listener idiom. This avoids building the names
of listener modules into the application. An independent author could write a
plug-in to this application: she would need only have the user modify //config.pl//
to include mention of the plug-in. Of course, modification of //config.pl// could
be automated. The install program for the plug-in would need to ask the user where
the //config.pl// is, and use the ConfigFile idiom to update it.Extending Through Scripting
pacakge UserExtention1;
The main application could prompt the user for a module to load, or
load all of the modules in a plug-ins directory, then make them available
as menu items in an "extentions" menu. When one of the extentions are
select from the menu, a reference to the application - or a FacadePattern
providing an interface to it - is passed to the run_macro() method of
an instance of that package. place_cursor(0, 0)
set_color(white)
draw_circle(radius=1)
set_color(red)
draw_circle(radius=2)
A few options exist: we can compile this directly to Perl bytecode using
B::Generate (suitable for integrating legacy languages without performance
loss), or we can munge this into Perl and ||eval|| it. Lets turn it into
Perl. # read in the users program
my $input = join '',
We're using the \\G regex metacharacter that matches where the last global
regex on that string left off. That lets us take off several small bites
from the string rather than having to do it all in one big bite. The
flags on the end of the regex are:
Out of context, the string "xyzzy" could be either a parameter or the name
of a method to call. The solution is simply to keep track of context: that
is where $state comes in. Every time we find something, we update $state
to indicate what class of thing would be valid if it came next. After
we find a function name and an opening parenthesis, either a hash
style parameter or a single, lone parameter, or else a close parenthesis
would be valid. We aren't even looking for the start of another function
[Though perhaps we should be. If changed this in our code, it would allow
us to nest function calls inside of each other. We would have to track
our level of nesting if we wanted to report errors if there were too many
or too few right-parenthesis. Exercise left for the reader.].
After a parameter, we're looking for either the close parenthesis or another
parameter.