Target Audience: This series is pitched at developers interested in Object Oriented Design Patterns using the Moose framework in Perl. I cover the examples from the Head First Design Patterns book, replicating Java implementation in Moose. You need to obtain a copy of the book, which is not hard to come by.
When I was at the University of Colorado, I spent a chunk of my life driving a trusty Toyota truck in the Rockies. In the winter, it was not unusual to encounter a snow-storm that dumped enough snow to block the mountain passes. In those climes during those climbs, one would benefit from tuning into an AM radio channel that broadcast information upon the weather and road conditions.
So, how about a display that plugs-in the Weather Station to show current Temperature, Pressure and Humidity? That's just what 'currentConditionsDisplay' does.
package currentConditionsDisplay;
use Moose;
extends 'Observer', 'DisplayElement';
has 'temperature' => (
is => 'rw',
isa => 'Value',
);
has 'humidity' => (
is => 'rw',
isa => 'Num',
);
has 'weatherData' => (
is => 'rw',
isa => 'WeatherData',
trigger => \®isterMe,
predicate => 'has_Subject',
clearer => 'clear_Subject',
);
sub registerMe {
my ($self, $weatherData) = @_;
$weatherData->_registerObserver($self) if $self->has_Subject;
};
after 'update' => sub {
my $self = shift;
my ($temperature, $pressure, $humidity) = @_;
$self->temperature($temperature);
$self->humidity($humidity);
$self->display;
};
after 'display' => sub {
my $self = shift;
print ">> NOW conditions >>";
printf " TEM <%10.2f>\n HUM <%10.2f>\n", $self->temperature, $self->humidity;
print "<< ----- ROGEROUT <<";
};
See how the 'Observer' and 'DisplayElement' roles are composed into this display? There can be any number of plug-in classes developed for the Weather Station API. This allows the 'Observer' role to be mixed-in a plug-in class, leaving the developers of that class free to pimp their ride.
Notice that the Observer holds a copy of (i.e. reference to) the Subject. The 'predicate' method 'has_Subject' checks that such a relationship exists. The 'clearer' method 'clear_Subject' severs it.
What about the 'trigger'? When the 'weatherData' slot is set, the trigger ensures that the display is registered with Weather Station as an observer by invoking the Weather Station's '_registerObserver' method. What is the significance of the underscore in '_registerObserver'? That indicates this is a private method, not exposed for public consumption.
Let's roll this out now.
First, a Weather Station as an object of 'WeatherData' class:
my $weatherData = WeatherData->new;
Add an observer:
my $myDisplay = currentConditionsDisplay->new(weatherData => $weatherData);
And Lights, Camera, Action!
$weatherData->setMeasurements(107, 64, 27.5);
Et voila! Gentlemen, we got ourselves a Weather Station!
Now how about one that does things a little differently? Shows the average of the last five readings, perhaps? Here we go - Gentlemen, Ladies and Larry - I give you: 'averageConditionsDisplay'.
package averageConditionsDisplay;
use Moose;
extends 'Observer', 'DisplayElement';
has 'temperature' => (
is => 'rw',
isa => 'ArrayRef[Value]',
default => sub { [] },
);
has 'pressure' => (
is => 'rw',
isa => 'ArrayRef[Num]',
default => sub { [] },
);
has 'humidity' => (
is => 'rw',
isa => 'ArrayRef[Num]',
default => sub { [] },
);
has 'weatherData' => (
is => 'rw',
isa => 'WeatherData',
trigger => \®isterMe,
predicate => 'has_Subject',
clearer => 'clear_Subject',
);
sub registerMe {
my ($self, $weatherData) = @_;
$weatherData->_registerObserver($self) if $self->has_Subject;
};
before 'update' => sub {
my $self = shift;
shift(@{$self->temperature}) if (scalar(@{$self->temperature}) == 5);
shift(@{$self->pressure}) if (scalar(@{$self->pressure}) == 5);
shift(@{$self->humidity}) if (scalar(@{$self->humidity}) == 5);
};
after 'update' => sub {
my $self = shift;
my ($temperature, $pressure, $humidity) = @_;
push @{$self->temperature} , $temperature;
push @{$self->humidity} , $humidity;
push @{$self->pressure} , $pressure;
$self->display;
};
after 'display' => sub {
my $self = shift;
my $tallies = $self->total_me;
print ">> MEAN conditions >>";
printf " TEM <%10.2f>\n PRE <%10.2f>\n HUM <%10.2f>\n",
$tallies->{temperature}/5,
$tallies->{pressure}/5,
$tallies->{humidity}/5;
print "<< ------ ROGEROUT <<";
};
sub total_me {
my $self = shift;
my %netValues = ();
my $index = 0;
foreach my $thisTemperature (@{$self->temperature}) {
$netValues{'temperature'} += $self->temperature->[$index];
$netValues{'pressure'} += $self->pressure->[$index];
$netValues{'humidity'} += $self->humidity->[$index];
$index++;
};
return \%netValues;
}
What does this do differently? Let's see..
Firstly, temperature, pressure, humidity are stored in containers - arrays. That's nice because it allows averaging over a moving window. How is this moving window achieved? Enter Method Modifiers - a Moose special! If you ask a passing Moose, he (or she) will tell you that a Method Modifier is a way of hooking into methods. On prodding further, the Moose may reveal that Method Modification is closely aligned with inheritance and enables behavior-modification so that a child class can develop specialized behavior to focus on a problem-solving task.
In the instance at hand, for example, the 'update' behavior is altered with 'before' and 'after' modifiers. There is nothing special about 'after' which, after all, only implements a definition left out in the abstract 'Observer' parent class. The usage of 'before' is interesting though, because this is where the size of the 'window' is controlled. Take a look and convince yourself.
Could we not simple have achieved this with Method Modifiers on the get/set methods of 'temperature', 'pressure' and 'humidity' slots? (These are 'temperature', 'pressure' and 'humidity' respectively.) One could define a 'before' method modifier upon each of these and check the size of the array, resetting it using 'shift' on the 'size equals 5' condition. Right?
Right?
Wrong!
Remember that Method Modifiers - 'before', 'after' - are called whenever the method is called. So if you call the method in its modifier, you end up with deep recursion. That makes nobody happy and the God-Father mad! Just kidding about the God-Father.
If this seems obvious - good! If not, work through it. This is important. The last part of this monograph in 3 parts upon the Observer pattern notes some handy Moose-isms, lest the novice Moose enthusiast be struck down with Moose-itis.
All code in this series may be downloaded from:
http://sites.google.com/site/sanjaybhatikar/codeunquote/designpatterns-1
http://sites.google.com/site/sanjaybhatikar/codeunquote/designpatterns-1
No comments:
Post a Comment