I've spent the day working on what has since become called Pixie, the object oriented database framework based around James's clever trick. Both I and the Fotango people have been working on 80/20 type solutions, but we appear to have different ideas about what constitutes the important 80%. So, I think I'll be merging tonight.
While I was beavering away on this, I finally got one of the classic OO commandments. This one's about constructors:
Your basic constructor, new should not take any arguments and should simply return an empty objectThis seems somewhat counter intuitive; you're always going to set the attributes to something or other, or you're going to something else at instantiation time. Well, yes. And that's what factory methods are for. A new that takes an argument is an accident waiting to happen once you start doing the funky inheritance thing; you're going to have to remember what kind of mangling will go on elsewhere in the class hierarchy and your head is going to ache. 'Simple' constructors can also help with things like Class::MethodMaker's very lovely 'object' directive.
package Object;
use Data::UUID;
use Time::Piece;
sub new {
my $class = shift;
my $self = bless {}, $class;
$self->initialize;
return $self;
}
sub initialize {
my $self = shift;
$self->init_oid
->init_timestamp;
}
sub init_oid {
my $self = shift;
$self->{_oid} = Data::UUID->new->create_str;
$self;
}
sub init_timestamp {
my $self = shift;
$self->{timestamp} = Time::Piece->new;
$self;
}
package Derived;
use base 'Object';
sub initialize {
my $self = shift;
$self->SUPER::initialize
->init_foo;
}
sub init_foo {
...
}
By returning $self even for setting methods (how often do you really use the return value of a setting method?) you can move to code like:
sub factory_method {
my $proto = shift;
my ($foo, $bar) = @_;
$proto->new->set_foo($foo)
->set_bar($bar);
}
which, to these old eyes at least looks neater (modulo the <code> tag screwing up my formatting) and takes less time to type. In perl 6 it'll probably look even neater.
Re:Returning $self
autarch on 2002-03-12T17:58:33
One of the modules in Alzabo does (the SQL constructor) so I can write code like this:
$sql->select(@things)->from(@tables)->where(@conditions)->order_by(@ other_things)->limit(10);Re:Returning $self
Matts on 2002-03-12T19:31:54
Sheesh! All that to avoid writing SQL??? Man, some modules need to get a life;-) Re:Returning $self
autarch on 2002-03-12T22:08:49
All that to be able to write SQL in a cross-platform manner.Re:Returning $self
djberg96 on 2002-03-14T18:42:14
Just in case you're only following replies to your own messages, see my hint above regarding Robin Houston's Want module. You can return $self or another value based on context that way.I should have read down further myself, as I just saw the remarks about ECODE.
Re:CODE tag
pdcawley on 2002-03-12T22:33:19
Weird. 'cos where I typed a two space indent I'm seeing four spaces in the output. Very annoying.Re:CODE tag
pdcawley on 2002-03-12T22:36:27
And, on closer inspection of the page source, spaces in the input have been replaced by ' ', that extra space appears to be what's screwing things up. (Unless it's 'Plain old text' and <code> failing to get on)Re:CODE tag
pudge on 2002-03-12T22:42:05
I dunno. I will soon be adding ECODE (it is there now, but I need a few more fixes for it) which will allow you to seamlessly integrate blocks of code with regular text in plain text or HTML modes. Maybe tomorrow morning (I have some more testing to do with that and one other thing).
Re:Factory methods and defered binding
pdcawley on 2002-03-12T22:49:09
Bugger. I could have sworn I typed
$class->new->set_foo($foo)
->set_bar($bar);
[fx: Hits 'edit' and fixes the code]
And I am aware of the late binding stuff. Perl makes that sort of thing so easy it's almost second nature now. In the Pixie::Proxy stuff I've been working on I have subclasses Pixie::Proxy::HASH, Pixie::Proxy::ARRAY etc. Pixie::Proxy::make_proxy($target, $store) looks at the target object to find out which subclass to instantiate, then does
$class->new
->set_target($proxy)
->store($store);
and returns a proxy object in the appropriate class. It's lovely.
I've never really understood why people don't seem to get factory methods; surely they're obvious. I remember reading Design Patterns and looking at the factory method pattern and wondering why they'd bothered to write it down.
One also never knows whether accessors like $wooz->name("foo") "should" return nothing, return $wooz, return "foo", or return the old value of $wooz->name(). If it's an arbitrary choice, I usually figure that relying on it just for style, is usually a bad idea. But that's just me.
As to options in constructors, I agree. Just about the only
constructor that I appreciate taking an option is HTTP::TokeParser's
->new(\$document) or ->new("filename"). But that's 'cause TokeParser is
a model of whipupitude, not internal neatness or subclassability. If I
were to do it over, I'd make a parameterless ->new(), and then make
a shortcut ->new_from_source($source) (and call it such in the docs)
which is just this
sub new_from_source {
my($class, $source) = @_;
my $x = $class->new;
$x->source($source);
return $x
}
Re:smaltakeetakee
pdcawley on 2002-03-13T09:22:21
Applying the 'die early' pattern I'd have to say that I'd implement it with something like:Does $wooz->save() return $foo or the success of saving it
sub save {
my $self = shift;
$self->_do_real_save or
die IOException->new
->obj($self)
->msg("Couldn't save $self");
return $self;
}
And I if I found myself using that exception idiom a lot, I'd be tempted to write a method somewhere up at the top of my class tree (UNIVERSAL perhaps) like:
sub throw {
my $self = shift;
my $exception = shift;
die $exception->object($self);
}
And refactor Exception's methods a little so that a typical one would look like:
sub set_attr {
my $proto = shift;
$proto = ref($proto) ? $proto : $proto->new;
my $attr = shift;
my $value = shift;
$proto->{$attr} = {$value};
return $proto;
}
sub msg {
my $self = shift;
@_ ? $self->set_attr(msg => shift) : $self->{msg};
}
Hmm... that's a bit ugly. I'm actually more likely to seperate getters and setters.
Anyhow, the original example becomes
sub save {
my $self = shift;
$self->_do_real_save or
$self->throw(IOException->msg("Couldn't save $self"));
}
Mmm... lovely.
As for the 'should setting methods return the new/old value or the object question, remember that generally the only thing that should be manipulating an objects attributes is the object itself. Outside objects monkeying with an object's state directly is often a 'Code smell'. So write the accessors/setters to do what is useful to you. Document that fact and let the rest of the world go hang. If you find you're doing $self->attrib($value)->attrib more often than doing $self->attrib1($value)->attrib2($value2) then don't return the object, return the value, or write your attribs as lvalue subs, or whatever...Re:smaltakeetakee
djberg96 on 2002-03-14T14:13:49
Does $wooz->save() return $foo or the success of saving itHere is where Robin Houston's Want.pm module gives you the power you need to decide - CONTEXT.
$wooz->save;
$wooz->save->print;
$val = $wooz->save;
someMethod($wooz->save);
@val = $wooz->save;These can all be differentiated with Want. So, using TorgoX's example, I'd code it like this:
use Want;
sub save{
my $self = shift;
$self->_do_real_save or die "Blah...";
if(want('OBJECT')){ return $self }
return 1;
}Sorry about the lack of indentation. The CODE tag doesn't seem to work as expected. How do people indent code here?
Re:smaltakeetakee
pdcawley on 2002-03-14T15:05:26
In my case I use the CODE tag, the occasional , and multiple drafts.How do people indent code here?Re:smaltakeetakee
pudge on 2002-03-15T17:26:32
The new ECODE tag is now supported!Re:smaltakeetakee
pdcawley on 2002-03-14T15:14:56
want('OBJECT') works? Oh wonderful. That means it might be possible to do Smalltalk type method selectors:
...
$dictionary->at($index)->insert($value);
...
$foo = $dictionary->at($index);
...
Class::Smalltalk anyone?
No, I will not be working on such a class myself in the immediate future, but it would be a neat hack.Re:smaltakeetakee
djberg96 on 2002-03-14T16:29:33
want('OBJECT') works? Oh wonderful.Yep! I discovered Want somewhat by accident, when people told me that the kind of contextual information I wanted wasn't possible. That's when I decided to scour CPAN. My search paid off.
It was his module (and Ruby) that inspired my Set::Array and Set::String modules.
The only problem right now with Want is that it doesn't get along with overload. You can do boolean tests with overloaded ops, but that's about it. Anything else will cause a crash. Using the debugger with Want will almost certainly cause it to crash as well.
Robin is aware of these issues. I eagerly await
.06! Re:smaltakeetakee
robin on 2002-03-14T20:17:05
Thanks for reminding me!