Forgive me for coming in late to this discussion. This topic has been on my mind a while because I ran into it with a system that Randal and I (more Randal, actually) built around the Prototype design pattern.


The original question from Terrence wanted method chaining to work even if a method call in the middle returns false (when a non-object value will stop the program with "Can't call method on ..." error).

        $obj->methoda->methodb->methodc->methodd

The problem is that none of those methods should return anything but an object. The interface allows for method chaining, so the return value has to be something you can call a method on. Any solution that tries to get around that is going to conflate the code complexity and size.

The solution is a pattern that uses a tiny object. There's a module of CPAN that tries this (Class::Null), but I generally think that using patterns are less about including certain modules and more about how you think about the universe. You don't have to follow the Gang of Four or whatever other religion exists in DP circles. You just have to understand and use the concept.

In this case, you need an object that represents false, and you can call any method on it without anything happening. You won't need black magic, additional abstractions, or more layers of references. You also don't have to build code you have to wrap in eval, which is really just building code that will break and putting some duct tape around it.

Here's how I would structure it:

Create a singleton class that has an object that will represent a false value. Everywhere you need a false return value in a method, you use this object. You should have a symbolic version of your failure value anyway (death to magic numbers), so now that symbolic value just has a different value.

Here's the basic idea. This is about the size of the real package. The AUTOLOAD routine just returns the object. It doesn't matter what the method name is, because every method responds the same way.

        package Local::False;

        my $false = undef;

        sub new { $false ||= bless {}, $_[0] }

        sub AUTOLOAD { $_[0] }

        1;

In your package that has a method chaining interface, you get a reference to the false object. Store that where you like, and whenever you want to return a failure, return that object. The method chain will continue, and you'll get the false object back at the end if any of the methods fail.

        package Local::Whatever

        my $false = Local::False->new();

        sub new { ... }

        sub methoda {
                ...;
                if( ...success... ) { return $self }
                else                { return $false }
                };

        sub methodb {
                ...;
                if( ...success... ) { return $self }
                else                { return $false }
                };

So, you always end up with an object as the result no matter which method in the chain fails (or the last method doesn't return an object,but that would be inconsistent).

        my $result = $obj->methoda->methodb->methodc->methodd;

From there, it's up to you to decide how you want to test $result to see
what happened. I'd go with something that looks like whatever else you are doing: just don't use a method in Local::False to do it. You'll have to remove that method name from the interface of the Local::Whatever module because it can no longer be chained. You can check if the chain failed by checking which sort of object you got back or adding a method to the Local::Whatever class that returns something that isn't an object:

        if( $result->success eq YES ) { ... }

        unless( ref $result )         { ... }

etc.

This pattern can get a lot more fancy too. You can give each class it's own false object, have the false object carry along an error message, and other things, but if you want to do that, you probably want to scrap the singleton pattern, or modify it for a singleton pool.

And if you want to know which method failed, then why are you chaining methods? :)

This may fit into what you are doing, or not. The big advantage is that the magic happens in code that the programmer doesn't have to type, code doesn't have to stop working for you to catch the error, and it's actually a tiny change if you already have good design.


-- brian d foy <[EMAIL PROTECTED]>

_______________________________________________
sw-design mailing list
[EMAIL PROTECTED]
http://metaperl.com/cgi-bin/mailman/listinfo/sw-design

Reply via email to