Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options

Object insanity and data hiding

by synapse0 (Pilgrim)
on Sep 09, 2001 at 09:27 UTC ( #111249=perlquestion: print w/replies, xml ) Need Help??

synapse0 has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks.. I finally have something worth posting again (or maybe i'm just sleep deprived rambling).. and it goes a little something like this:
I'm working on a web game, which will hopefully be into it's beta testing phase soon so I can get my friends to waste their precious time again :) The game is a fairly large project, whose goal was to teach me some more about OO, DBs, and CGI funstuffs...
As I was looking through the code today, I noticed that one of my first objects, a little guy called ServerInfo didn't do much in the way of enforcing data hiding. Basically, ServerInfo's main duty is to read a configuration file and control the variables that come from it.
Well, for the longest time (up till about an hour ago) ServerInfo basically read the config file variables into a hash, and then I would refer to those variables like:
my $si = new ServerInfo; $si->configure; print "This is one of the variables: $si->{'VARIABLE_X'}";
As you can see, it's just a hash holding it. Nothing prevents someone from accidentally doing something like $si->{'VAR_X'} = "No, you're not supposted to _assign_ me, you fool!". So I got thinking, how do i promote data hiding? I can't create a bunch of individual methods like $si->variable_x(), since the data in the config file changes... So, onto my solution..
What I ended up doing, in my brainded-awake-for-24+-way-too-long-hours state was to modify the configure method just slightly, so that it keys the hash variables with an opening "i'm magic, don't touch me" underscore. So, my variable becomes $si->{_VARIABLE_X}. And created another method, get() which appends an underscore to the beginning of it's argument and returns what is in the hash. Now I don't have to worry about someone doing $si->get('VARIABLE_X') = "ha! you can't touch this".
So, what i'm wondering is, is my solution a good one? or will i wake up tomorrow, look at the code and wonder what the hell I was thinking? I figure making the variables harder to play with (Oh, sure, ppl can still assign to $si->{_VARIABLE_X}, but they would have to do it intentionally, and we all know intentionally messing with magic variables is a no-no in most instances) is a good thing, but now I have to access a function rather than just accessing the hash.. I'm not sure yet if there will be much in the way of a performance loss... Any other thoughts on how to handle something like this? code critique?
Thanks monks..
And here's the code and a bit of the configuration file...
(oh, and this was also my first toying with POD, fun)
# ==================================================================== +== # # Server Information object # # Copyright (C) 2001 Art C. Pacheco (Synapse0) All rights reserved. # # ==================================================================== +==== package ServerInfo; use strict; # ==================================================================== +== # /* ----------------------------------------------------------------- +-- */ sub new { my $class = shift; my $self = {}; bless($self, $class); # Grab the name of the calling executable. my $x = $0; $x =~ s#.+/##; $self->script("$x?"); return $self; } # ==================================================================== +== # script(scalar) # sets/returns name of script # /* ----------------------------------------------------------------- +-- */ sub script { my $self = shift; $self->{_script} = shift if @_; return $self->{_script}; } # ==================================================================== +== # assign(KEY, VALUE[, VALUE, VALUE, ...]) # assigns key->value pairs to ServerInfo hash # /* ----------------------------------------------------------------- +-- */ sub assign { my $self = shift; my $field = shift; my $item; $field = "_$field"; if (@_ > 1) { # we were pushed an array while (@_) { $item = shift; push(@{$self->{$field}}, $item); } } else { # we were pushed a single item $item = shift; $self->{$field} = $item; } } # ==================================================================== +== # configure() # reads in configuration file ( ./_config.inf ) and assign()s the # key->value pairs specified # /* ----------------------------------------------------------------- +-- */ sub configure { my $self = shift; my($cfg_var, $val) = ('',''); if (open(FILE, "< _config.inf")) { while (<FILE>) { # skip anything that's not a directive, untaint next unless my($line) = ($_ =~ /^(\w.*)/); chomp($line); ($cfg_var, $val) = split(/\s+/, $line, 2); $val =~ s/{(.*?)}/$self->get($1)/eg; # resolve any {vars} if ($val =~ /^\@ (.*)/) { # $val holds a list $self->assign($cfg_var, split(/\s+/, $1)); } else { $self->assign($cfg_var, $val); } } close(FILE); } else { # do something about open error print "ACK!! NOOOOOOOOOOOO : $!\n\n\n\n"; } # debug $self->assign('DEBUG', -e "$self->{_BaseDir}/DEBUG" ? 1 : 0); } # ==================================================================== +== # get(scalar) # retrieves a value (scalar or list) assigned by assign() # /* ----------------------------------------------------------------- +-- */ sub get { my $self = shift; my $key = shift; return $self->{"_$key"}; } # /* ----------------------------------------------------------------- +-- */ 1; __END__ =head1 NAME ServerInfo - Server information object =head1 SYNOPSIS # create a new server object and execute configuration use _Mod::ServerInfo; my $si = new ServerInfo; $si->configure; # access a configuration variable # used to be $email_link = $si->{AdminEmailLink} # but now it's safer $email_link = $si->get('AdminEmailLink'); =head1 DESCRIPTION ServerInfo encapsulates configuration information obtained from the _c +onfig file in the root directory. =head2 OBJECT METHODS =item $si = ServerInfo->new() Creates a new ServerInfo object. The constructor initializes the script() method described next. =item $si->script([scalar scriptname]) If given an argument, script() will set the name of the current script +. If called without an argument, script() will return the script name. script() is initialized when a new ServerInfo object is created. =item $si->assign(scalar key, scalar/list value) Takes two (or more for an list) arguments and creates a hash, assigning the value(s) to the key. =over 4 =item example: $si->assign('KEY', 'VALUE'[, 'VALUE', 'VALUE', ...]) =back =item $si->configure() Configuration method. Reads in data from the configuration file B<_config.inf> which lives in the same directory as the ServerInfo mod +ule. configure() reads in the data and splits the info at the first whitesp +ace, using the lefthand data for the key, and the righthand data for the va +lue (which can be a list). It uses assign() to place the data into a hash, accessible by the ServerInfo object. =item $si->get(scalar key) Retrieval method. Returns a value assigned by the assign() method =head1 AUTHOR INFORMATION Copyright 2000-2001, Art Pacheco. All rights reserved. =cut
config file:
BaseDir /home/tech/htdocs/arena BaseURL http://dev/~tech/arena # # Intro script (index script) # IntroURL {BaseURL}/arenaintro.cgi? IntroEXE {BaseDir}/arenaintro.cgi # # Some Default Colors # DefBody: bgcolor text link vlink alink DefBody @ #000000 #99c68e #99c68e #99c68e #bcc7c7 # Table background color TableBgColor #333333 # Hilighted text hilight #c9e6be # bold text bold #c7aa7d # undefined color UndefColor #555555 # # Battle colors # Char basic attack color ChBasAtt #559955 OppBasAtt #00aaaa ChAttHit #dddd55 OppAttHit #dd0055 ChMiss #77aa77 OppMiss #22bb99 ChArmAbsorb #658978 OppArmAbsorb #008888 ChSpecAtt #bbbb33 OppSpecAtt #bb0033 ChCounter #88cc88 OppCounter #33dddd

Replies are listed 'Best First'.
Re: Object insanity and data hiding
by blakem (Monsignor) on Sep 09, 2001 at 09:49 UTC
    I would highly recommend reading Damian Conway's Book Object-Oriented Perl for a project like this. Just flipping through my copy, I came accross this example on page 92, where he address the issue of data hiding by using the AUTOLOAD function.
    package CD::Music; use strict; use vars '$AUTOLOAD'; # Keep 'use strict' happy { my %_attrs = ( _name => undef, _artist => undef, _publisher => undef, _ISBN => undef, _tracks => undef, _rating => undef, _room => undef, _shelf => undef, ); sub _accessible { exists $_attrs{$_[1]} } } sub AUTOLOAD { my ($self) = @_; $AUTOLOAD =~ /.*::get(_\w+)/ or croak "No such method: $AUTOLOAD"; $self->_accessible($1) or croak "No such attribute: $1"; $self->{_read_count}++; return $self->($1); }
    This will dynamically create methods such as getname to access the underlying data, rather than letting someone poke at it willy-nilly. This keeps your access methods and your data in sync. It also allows for easier error checking, since all your methods are being dynamically defined in one place.


      I don't think this will help: synapse0 is using a generic get() because s/he doesn't know what the keys of the config hash are going to be. explicit methods - or autoload fields - won't fit.

      As for the question of how to make sure the contents of the config hash are only visible to the prescribed methods, I think the answer is probably a closure object, though i've never had occasion to use one this way. 'Closures as Objects' in perltoot should provide all that's needed.

A quick OO primer...
by dragonchild (Archbishop) on Sep 10, 2001 at 16:44 UTC
    You're still violating data-hiding. It took me over a year (and two separate projects) to realize that the method you're using (which I used extensively) isn't OO programming. All you are doing is associating functions with a hash, then unnecessarily abstracting a function call over every access to that hash.

    A quick primer on OO ... the idea is that you have these things and each thing can accomplish a certain set of tasks. These task are very abstract, like walk(), dispense_money(), and the like. Then, there is what asks that thing to do whatever. The asker doesn't know nor does he care to know how the thing gets its stuff done. All that matters is results. (In that sense, OO programming could be called "results-driven programming" ...)

    A human example would be a manager and an employee. Imperative programming would be like having a manager stand over your shoulder and say "Now, press 'N'. Now press 'o'. Now press 'o'. Now press '!'. ...". This is opposed to Object-Oriented programming where the manager says "Get this task done. You have a week. Tell me when you're done." The second manager doesn't know and doesn't care how you get the job done. He just wants to know that the job is done and done well.

    What does this have to do with your case? Well, I haven't read the code. Frankly, it sounds like you're doing imperative programming in OO-clothing. It feels clunky, doesn't it? Sorta like you're trying to get a square peg in a round hole. Well, rethink what you're doing. Identify the things in your design (you do have a design, right?) and identify what each thing can do. Then, let them go and do their jobs without your meddling interference! :-)

    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Thanks for the reply.. I'm trying to sort out how directly your statements describe my code. I think I have a competent idea of OO, but since i've never had anyone poke around my code and point things out, I dunno for sure.

      All you are doing is associating functions with a hash, then unnecessarily abstracting a function call over every access to that hash

      Well, in this particular case, that's precisely what this object is supposed to do. Really, it's just a config file regurgitator. Is there another way you handle something like this, where the object is there mostly to access specific data, and not make any changes to that data?

      I hope your go/othello project is going well..
        Frankly, I'd just use a hash. *grins* Call a spade a spade. Since you're writing the code, you know exactly how that hash is going to be used. If someone is stupid and violates the contract of that hash ("Don't assign to me cause I'm your configuration values!"), then any problems are their own fault.

        Objects are supposed to be things that do stuff, not things that act as repositories. (Of course, there're exceptions to every rule, but it's a good starting point.) Now, there is a way to make a good configuration object. You just have to change the way you think about it.

        What you could do is pass the configuration object a filename. You, as the main program, wouldn't know how the configuration file is set up or what all the things it specifies are. Then, what your main program would do is ask the object if XXX is set to nnn, maybe by doing something like

        if ($config->isSet('XXX', nnn)) { # Do something here } else { # Do something else here }
        All the parsing of the config file, maybe even writing of a new config file, and everything that has to do with configuration would be encapsulated within this object. The trick is to get the interface right. Your main program doesn't want to know the exact values ... it just wants to know if it can do something based on an attribute's value.

        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://111249]
Approved by root
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2023-02-06 12:04 GMT
Find Nodes?
    Voting Booth?
    I prefer not to run the latest version of Perl because:

    Results (34 votes). Check out past polls.