Replies: 4 comments 8 replies
-
I guess there are. I was a bit startled when reading about how you resolved the issue. By copying the field into six classes, there's nothing left for the base class to do, so there is hardly any reason left for inheritance. Reading the section describing the tokens in HTML::TokeParser confirms: The only thing these concrete classes have in common is "they are initialized from an array reference". This is a situation I have encountered as well: A constructor parameter which isn't really helpful as an object field. role Role::TokeParser::Token {
field @token;
ADJUST (:$token) {
@token = @$token;
}
method token {
return @token; # or deep-clone individual elements as desired
}
} This has a public reader for the token. It does not pass a reference, so it is safer than having a reader for There's also an alternative: the classes can pick the relevant values at construction time: class HTML::TokeParse::Corinna::Tag::Start {
field $tag;
field %attr;
field @attrseq;
field $text;
ADJUST (:$token) {
my $class_indicator = $token->[0]; # if not already stripped by a factory
$tag = $token->[1]
%attr = $token->[2]->%*;
@attrseq = $token->[3]->@*;
$text = $token->[4];
}
} This alternative works without extra modules but does not offer the possibility for a role (or common ancestor) to provide methods which mangle the token array as a whole. |
Beta Was this translation helpful? Give feedback.
-
@HaraldJoerg Since we don't yet have roles, I've gone for the second approach and my class look very similar to what you have, though I've not yet pushed the changes. For your role, though, we still have the case that I should be able to choose whether I want to expose the Also, keep in mind that for objects, we want the interfaces to be as small as possible. Only give consumers what they need. There's no need for the token since all the working bits are there, so again, I don't want to expose it. |
Beta Was this translation helpful? Give feedback.
-
Oh, and you wrote that there's nothing left for the base class to do, but that's not the case. Predicate methods: class HTML::TokeParser::Corinna::Token :isa(HTML::TokeParser::Corinna::Base) {
use HTML::TokeParser::Corinna::Policy;
method is_tag {false}
method is_start_tag {false}
method is_end_tag {false}
method is_text {false}
method is_comment {false}
method is_declaration {false}
method is_pi {false}
method is_process_instruction {false}
} But now you might be wondering what class HTML::TokeParser::Corinna::Base {
use HTML::TokeParser::Corinna::Policy;
method AUTOLOAD {
our $AUTOLOAD;
my ( $class, $method ) = ( $AUTOLOAD =~ /^(.*)::(.*)$/ );
return if $method eq 'DESTROY';
throw(
'MethodNotFound',
method => $method,
class => $class,
);
}
} I'm converting to use proper exceptions, so if someone calls a missing method, they'll get an exception object instead of a string (though that's currently a string because |
Beta Was this translation helpful? Give feedback.
-
Something I'd like to throw out here: my reading of the proposed solution still has these trusted methods invoked as I would suggest we instead leverage lexical symbols to handle this task. I floated the idea of a |
Beta Was this translation helpful? Give feedback.
-
A quick reminder about encapsulation. It's not about hiding state. In OOP, to the outside world a class is nothing more than a complex type, an expert about a particular problem domain. The implementation is opaque. This is why a web server is a perfect "object": your browser generally can't screw around with its internals (barring security issues).
Externally a class is an expert, but internally that expertise is managed by coupled state and process. So for this discussion, we'll want to avoid temptations to just export state to other classes. It needs to be state plus process.
In a project where I'm using
use feature 'class';
to look see how we're doing, I defined afield $token :param
(which takes an array reference) in an abstract base class. Six concrete children can all be instantiated as$class->new( token => $token )
and need access to this data, so I had to providemethod _get_token {$token}
in the base class. This means anything outside the class could get access to the state, violating encapsulation. I resolved this by hard-coding the following in all six concrete classes.field $token :param;
Obviously, cutting-n-pasting is not good. If I later need to add additional constructor constructor parameters, I'd have to cut-n-paste a lot of boilerplate in six different classes. This is fragile. In fact, the original traits research required stateless traits, (traits provided methods, but no instance data). This turned out to be fragile and research has been done to work around the issues I'm encountering with this limitation.
I think we can resolve this with trusted (protected) methods. The Corinna team has discussed trusted methods a few times and I think it's time to revisit them.
For many programming languages, a trusted method is like a private method which can only be called by subclasses or both other code in the same project. Since Perl doesn't have a well-defined notion of project the latter solution is not good. However, trusted methods being infinitely inheritable means someone can create a subclass of your class and gain access to
field $password;
This is also not good.So here's what I'm thinking:
For the
field $token
above, I think we can define attributes to declare auto-generated methods as trusted or private.As a side benefit of the above, consider my token: it's an array ref. I could convert
field $token :reader(:trusted)
to this:The interface remains identical, but now I can use properly coupled state plus behavior to ensure that subclasses can't accidentally break my code.
Of course, this works for roles, too:
Are there other ways of fixing this?
Beta Was this translation helpful? Give feedback.
All reactions