Re: Perl and Objects
by mirod (Canon) on Apr 10, 2001 at 15:51 UTC
|
I have a very simple rule of thumb when it comes to OO:
OO belongs in modules.
Modules are an intermediate level, where I build abstractions
that will probably be re-used by several programs. There
it makes sense, if only in order not to pollute the
main namespace, to have a clean, opaque, interface and to
wrap data structures and methods.
On the other and, at application level, I have seldom
found the need to use OO design. On the contrary I often
find that it would get in the way, as this is the level
where most of the time abstractions hit the reality wall
(I could have used an other expression involving stuff hitting
revolving blades) and where good ole top down procedural
design usually works well.
Of course this is not an absolute rule but I have found that it
worked pretty well for me, with the added advantage of letting
me be abstract, clean and happy with my code in OO modules
while I know that application-level code is always a little
messier and I have to live with it.
| [reply] |
Re: (Zigster) Perl and Objects, how do strongyou/strong resolve the two?
by zigster (Hermit) on Apr 10, 2001 at 16:23 UTC
|
As a good friend of frankus I follow his posts with interest, I am presently seeing a trend. I know him to be a very adept perl programmer and note he is trying to learn the complexities of OO programming. I find this heartening as a OO programmer myself.
I am worried however, I am worried about coupling any language and the oo paradigm (damn I hate that word) too closely together. I wrote a deliberatly contetious node a while ago where I claimed that perl is not OO, I continued to agree with comments from fellow monks that neither is java or C++. I say this as a very experienced java/C++ programmer. OO is a design concept that has support within languages. It is therefore not a good idea to start from a language and work back. Start with a design then apply the design to a language. The resulting code can use any feature of the language you so chose OO or otherwise, as long as it faithfully maps to the OO design.
I am not a fan of the syntax of OO perl so I tend to avoid it. I still write OO perl just without the OO syntax.
Start with analysis, try writing a UML / SM design for your next module then code based on that design. Ignore the OO nature of perl. Use the aspects of the language you know well. That will mean you are learning only one thing at a time. Once you have mastered OOA then move to OOD and try to use the OO aspects of perl, they are not to my taste but they are very complete and very usable. You will then fit them into your understanding of OO instead of trying to fit your understanding of OO into perls interpretation.
Question what you do, try to see under the hood of OO. It pains me how often I see great programmers missing the point of OO programming because they dont see past the fact that it usually requires more 30 chars to do what perl can do in 5 "Obviously, You Will Need a Java Course...". Sorry I have drifted from the point, I will leave it there and follow this thread keenly.
Regards
--
Zigster | [reply] |
Re: Perl and Objects, how do strongyou/strong resolve the two?
by merlyn (Sage) on Apr 10, 2001 at 18:56 UTC
|
| [reply] |
Re: Perl and Objects, how do strongyou/strong resolve the two?
by Masem (Monsignor) on Apr 10, 2001 at 15:52 UTC
|
As for good examples, I would simply look at some of the more popular modules that are referred to here often but are more than just wrappers around common functions, such as CGI.pm, DBI, Bit::Vector, and more. As for bad code... well, there's a lot of it floating around there.... :D
From all that I've read here and elsewhere on OO Perl, it's not the same as any of the other major OO languages; you can break a lot of the typical rules that 'Object Oriented' applies to, and be selective about which rules that you want to use in your code when you do OO in Perl. The fact that Perl doesn't have strong type checking also allows many more rules to be broken. The best that you can do is at least encapsulate variables, have private and public functions, and provide new instances of an object as to keep everything working. While you could easily make a 100% complete OO perl program from that, most strive to instead to encapsulate as much as they can into objects, and then use standard procedural programming with object function calls as to simplify and increase the legibility of the code.
Dr. Michael K. Neylon - mneylon-pm@masemware.com
||
"You've left the lens cap of your mind on again, Pinky" - The Brain
| [reply] |
|
|
Please don't try to read CGI.pm as an example of how to do
OO properly. *shudder*
CGI.pm is a great module and it is currently the only
option I can recommend for parsing of CGI forms. But I
cringe when I think of someone reading its source code
trying to figure out how to do OO. Trying to read CGI.pm's
source code to figure out how, for example, the table()
method works is challenge enough (for one thing, there
is no table() method in the source code).
I have a sneaking suspicion that DBI is likewise rather
dense code and not something to be read in an attempt to
understand OO.
Bit::Vector is all written in C code which makes it a
pretty bad example as well. Bit::Vector::Overload might
be a better choice but since it makes heavy use of
Bit::Vector I still can't recommend it.
The only example OO modules that pop into my head are
examples of OO mistakes. For example, Data::Dumper has
an awkward interface. My Win32::TieRegistry should use
a separate package for the tied hash. Array::Compare
needs to provide non-OO alternatives. Exporter.pm and
DynaLoader.pm shouldn't be using inheritance.
But that makes sense since part of the point of a
well-written module is that you don't trip over interface
mistakes and don't run into bugs that prompt you to read
the source code. (:
-
tye
(but my friends call me "Tye")
| [reply] |
|
|
What do you think of the design of the LWP library?
| [reply] |
|
|
|
|
Please do not call it strong type checking.
Call it static type checking. Versus dynamic typing at
runtime.
Now read a few articles from which you can form your own opinions on whether static typing (at least as practiced by langauges like C++ and Java) is the greatest idea in the world...
UPDATE
Erm, On of those statics was supposed to be a strong and
now is...
| [reply] |
Re: Perl and Objects, how do you resolve the two?
by stephen (Priest) on Apr 10, 2001 at 21:44 UTC
|
Actually, I don't see anything wrong with creating accessor or mutator methods with Autoload. Class::MethodMaker does that, and it's very useful for simple classes. I've used Autoload to come up with accessor methods. The trick is to document the methods as though they were normal. After all, implementation shouldn't be part of the interface anyway.
Worrying needlessly about syntax, and which is better, seems counterproductive to me. Any vaguely workable syntax becomes transparent after about a month of use. :)
If you like Perl, but enjoy your object orientation more pure than Perl offers, you might want to look at Ruby. It's not a Perl-killer yet-- it lacks DBI classes AFAICT, among other things-- but it has some of the purest object-orientation I've ever seen. (Example: what do you do when you want something looped five times? You tell the number five, "Do this!")
It's true that doing object-oriented Perl requires more self-discipline than some other languages. I actually see this as a good thing. Writing OO Perl forces you to think about your design and stick with it. In more enforced object-oriented contexts, it's easier to forget that OO is a design quality, not a language quality.
Oh, and to metoo merlyn, you should definitely, definitely read Object-Oriented Perl.
Update: In the interest of accuracy I'll correct myself-- Class::MethodMaker doesn't generate accessors on autoload; it autogenerates
them on class load. In terms of the argument, though, I personally don't see a difference, as such matters would be hidden from other classes.
stephen
| [reply] |
|
|
Actually, I don't see anything wrong with creating accessor or mutator methods with Autoload. Class::MethodMaker does that, and it's very useful for simple classes. I've used Autoload to come up with accessor methods. The trick is to document the methods as though they were normal. After all, implementation shouldn't be part of the interface anyway.
Because it defeats the whole idea of having them. The point is you hide the information behind a method interface so if you change the structure of the object then you do not break the interface, there are other reasons but that is sufficient for now. If you have automatically generated methods (which if I understand correctly that is in effect what autoload does) changing the structure of the object will change the interface. Information hiding is a fundamental part of OO and it is not just about having methods to call, you need to understand what is under the hood. This is what I was refering to when I talked about questioning OO ask why why why. A cursory glance at OO is not sufficient.
--
Zigster
| [reply] |
|
|
This completely does not match my experience.
For a random instance, if I initially write the class so
that a set of properties are exported based on the
structure of the object, and I restructure the object,
I can just add a few new methods to calculate the things
that AUTOLOADER is not doing for you. As long as you
remember that your interface needs to be stable, there is
no problem with autogenerating large chunks of that
interface. After all - thanks to the same principle that
you are trying to invoke - there is no need to know or
care about the implementation as long as it does the
right thing.
And from a maintainability point of view, autogenerated
interfaces can be very, very useful. They can be used to
avoid a lot of manual synchronization between pieces of
code. (Just try to write a class that proxies off of
another transparently without them!) Which is why many OO languages offer equivalents to AUTOLOAD. Perhaps it is
called something like method_missing. BFD. It is the
same thing and can be used in the same way for about the
same things.
| [reply] |
|
|
Ah, I think I see where the misunderstanding lies.
You ("you" meaning zigster) are assuming that I'm autogenerating methods based on the underlying object implementation. That's not what I'm talking about. I'm talking about generating methods based on some other standard, like so:
Note: code based on working code and Camel, but untested
package Foo;
use strict;
use vars qw($AUTOLOAD %ACCESSOR_TABLE);
use Carp;
%ACCESSOR_TABLE = (
'name' => '_name',
'id' => '_id_code',
'phone' => '_phonenum'
);
sub DESTROY { }
sub new {
my $type = shift;
my ($name, $id, $phone) = @_;
my $self = {
_data_table => { _name => $name,
_id_code => $id,
_phonenum => $phone,
},
};
bless $self, $type;
return $self;
}
sub AUTOLOAD {
my ($sub_name) = $AUTOLOAD =~ m{.*::(.*)$};
$sub_name =~ /^get_(.*)/ or croak "Can't autoload method $AUTOLOAD
+";
my $data_name = $1;
defined $ACCESSOR_TABLE{$data_name}
or croak "Can't autoload method $AUTOLOAD";
my $field_name = $ACCESSOR_TABLE{$data_name};
*$AUTOLOAD = sub {
(ref($_[0]) && $_[0]->isa('Foo')) or croak "Accessor method '$
+sub_name' called improperly";
return $_[0]->{'_data_table'}{$field_name};
};
goto &$AUTOLOAD;
}
And in other code...
use Foo;
my $foo = Foo->new('jenny', '24601', '408-867-5309');
my $phone = $foo->get_phone();
my $name = $foo->get_name();
Then, I can add accessors by merely messing with the %ACCESSOR_TABLE, and not defining more subroutines. This is particularily useful for objects which are loaded from databases-- I can add new fields to the database without doing as much recoding. If there are accessors that don't store their data in '_data_table', I can define them separately, or just add some functionality to AUTOLOAD.
These are implementation details; I just want to show that there are legitimate uses for AUTOLOAD in an object-oriented context. And once again, this illustrates the power of OO. From the perspective of the class user, it doesn't matter whether I've defined individual subroutines or if I've got them autoloaded instead. The class user doesn't need to understand "what is under the hood."
stephen
Update: Added an 'accessor exists' check. | [reply] [d/l] [select] |
|
|
|
|
|
|
The temptation is
to let Perl do everything it can, which
can include disregard OO best practices, so you have a
choice what OO practices to apply. For me accessor and
mutator processes clarify the relationship of data in a
method, so to have an Autoload function is almost self-defeating,
it says I want the advantages of accessors and mutators, but..
I'm not going to write them. (I like Autoload for this reason,
and this dichotomy is why I posted this, I need help %^)
There is a really cool implementation of an OO Autoload with
a list of valid attributes etc which I like.
OK, I know I didn't mention Mr Conway's exceedingly good book
in this node, but I was guessing, people might start getting
bored of me singing it's praises.
As for Ruby, I've got it on my machine, but I am still
toying with Perl. I think if I really wanted to do OO for
OO's sake I'd go with Eiffel or Smalltalk before Ruby.
| [reply] |
Re: Perl and Objects, how do you resolve the two?
by satchboost (Scribe) on Apr 10, 2001 at 23:13 UTC
|
Perl object-orientation isn't OO. Well, it is. You can create objects (known as modules), tell them what they are (by blessing them), and create method inheritance (through @ISA). From a very nuts'n'bolts view, that's basically all you need for functional OO. Encapsulation and Inheritance.
Data inheritance is often nice to have, too. But, that's easily done by being intelligent in your initialization function(s).
However, I have to vehemently disagree with the concept of using AUTOLOAD to do everything. Frankly, that's unmaintainable in the long run. (Not to mention being a little slower than previously-declared functions.) Here is a basic OO design I am currently using in a medium application and it works beautifully:
sub get_attribute_names_and_defaults {
my $pkg = shift;
$pkg = ref($pkg) if ref($pkg);
my @result = @{"${pkg}::_ATTRIBUTES_"};
if (defined @{"${pkg}::ISA"}) {
push @result, get_attribute_names_and_defaults($_) for @{"${pk
+g}::ISA"};
}
@result;
}
sub define_attributes {
my $self = caller;
my $pkg = ref($self) ? ref($self) : $self;
my $pkg_string = "${pkg}::_ATTRIBUTES_";
if (defined @{$pkg_string}) {
push @{$pkg_string}, @_;
} else {
@{$pkg_string} = @_;
}
}
sub new {
my $type = shift;
my %args = @_;
my $self = {};
bless $self, $type;
my $attr_name;
my $pkg = ref($self);
my %defaults = Global::Generic_Object::get_attribute_names_and_def
+aults($pkg
);
for (keys %defaults) {
$attr_name = Global::Generic_Object::_input_to_attribute(undef
+, $_);
unless (exists $self->{$attr_name}) {
my $default_ref = ref($defaults{$_});
if ($default_ref eq 'ARRAY') {
if (@{$defaults{$_}}) {
$self->{$attr_name} = [ @{$defaults{$_}} ];
} else {
$self->{$attr_name} = [];
}
} elsif ($default_ref eq 'HASH') {
if (@{$defaults{$_}}) {
$self->{$attr_name} = { %{$defaults{$_}} };
} else {
$self->{$attr_name} = {};
}
# } elsif ($default_ref eq 'SCALAR') {
# $self->{$attr_name} = \$$defaults{$_};
# } elsif ($default_ref) {
} else {
$self->{$attr_name} = $defaults{$_};
}
}
}
$self->_initialize(%object_hash, %args);
return $self;
}
define_attributes(-REGISTERED_NAME => '');
sub _initialize {
my $self = shift;
$self->set(@_);
return 1;
}
sub exists {
my $self = shift;
my @args = @_;
my @return_list = ();
foreach my $in (@args) {
my ($obj, $data) = split (/\./, $in, 2);
my $key = $self->_input_to_attribute($obj);
if (exists $self->{$key}) {
if ($data && ref $self->{$key} && ref($self->{$key}) =~ /:
+:/) {
push @return_list, $self->{$key}->exists("-$data");
} else {
push @return_list, 1;
}
} else {
push @return_list, 0;
}
}
return @return_list == 1 ? $return_list[0] : @return_list;
}
get(), set(), inc(), strcat(), and clr() all are defined in the same way. You have one function that handle attribute retrieval. That's it. Nothing more. Plus, this handles attribute inheritance as well.
Thoughts? | [reply] [d/l] |
|
|
How vehement do you want to get?
Try it out on me. :-)
First of all I would not advocate using AUTOLOAD for
everything. But there are cases where it is a
useful tool to know about. Use it wisely.
For instance how would you write the equivalent of
Re (tilly) 1: Nested Classes without AUTOLOAD? (At least and make it look
like Perl's object syntax.)
And yes. There is a performance hit. Which is why people
play games like I did at Re (tilly) 1: Reverse Inheritance Irritance and auto-instantiate
real methods. You pay the price of the AUTOLOAD once
only per method that you use. Incidentally the pattern
that I used in that post is a common one to see in a lot
of OO languages. Generally done with some version of
AUTOLOAD.
So I hope that by practical example you see that there
are things that AUTOLOAD is truly useful for. No, it
should not replace everything under the sun. But it is
a legitimate part of the toolkit.
As for the example that you showed, my main complaint is
that you are not using strict. Also I would want to
export the functions from Global::Generic_Object using
Exporter. Beyond that, I would need to know what your
definition of medium sized is. If you mean a few thousand
lines, I think you are waaay over-engineering it. If you
mean 30,000, well I wonder about over-engineering. In
a larger application? Plausible. But I would want to see
the object model.
In short, I would worry that is a design meant to support
an object model that has grown out of control...
As for whether Perl's OO really is, well it is simplistic,
it is a little wordy, performance isn't great, but you can
do OO and it works. You can play fun OO games and it works.
You can lay something complex out and it works.
But if you want OO nirvana, well Perl supports a lot of
styles of programming as "second class citizens", OO
among them. Perl is not the greatest language for OO.
But it works...
| [reply] |
|
|
Medium-sized is 30,000 lines of actual code and 70,000 lines of script-generated templates. In addition, this model allows for extremely easy extensions of the application. (In fact, for other reasons, it has been rebuilt to allow for this.) The object model is clean-ish, if you're wondering.
The way one uses that module is to inherit it through @ISA. Then, $self->get(-FOO) works quite nicely. $self->exists(-FOO) is included primarily for completeness than a real need for it. You don't Export the functions. (The only function I really want to Export is define_attributes, but I'm having problems getting define_attributes within the scope of grandchildren and further down. Any thoughts?)
I still return to my main objection concerning AUTOLOAD being that it's much less maintainable. I inherited this project and (hopefully!) will be passing it on to someone else in the next 6-8 weeks. If I was passing on an application containing some 250 classes, every single one of them having their own AUTOLOAD, that's a maintenance nightmare! This way, the accessor methods are defined in one, and only one, place. Every object has the exact same API, meaning that learning the system is very easy. (Yes, learning $self->foo() is also easy. I'm whining. But, having the API to within the objects be consistent is still a "Good Thing"(tm).) I hand this off and my successor doesn't have to fight the implementation. That is also a "Good Thing"(tm).
Yes, there are things that AUTOLOAD is good for. I would put forward that a production application isn't one of them.
| [reply] |
|
|
|
|