http://qs1969.pair.com?node_id=234012

Update: There is a version 2 of this that I consider better, but you should read this before reading version 2

Intro
When programming in OO Perl I prefer to keep everything, including configuration options, accessible via a method rather than directly accessing the objects data structure. The sometimes tedious part is adding all the methods as the configuration file grows. I don't want to hard code my accessor methods and I certainly don't want to modify my API modules everytime a new configuration option is added. What follows is a method for keeping both the API developer and the application developer happy and providing flexibility without complete loss of control. This was in part was started after I saw that many of the WWW::Search::X modules were directly accessing the object data structure and felt that level of access was "dirty."

A Good API
The purpose of an API is to provide building blocks for application developers. API modules once deployed are expanded, but the base functionality has to remain. These modules remain in use for a long time hopefully lasting for months or years without major revision once deployed. So how can we achieve this goal and keep an OO interface while expanding configuration options at the developer level and keeping the developer from creating their own accessors?

The Concept
We start off with the Class::Accessor module. Class::Accessor creates accessor methods within your class by passing a list of the method names you want generated into its mk_accessors method.

If you combine the Class::Accessor module with a configuration module such as Config::Auto you get a combination that makes it easier to add options to your applications as well as maintain and extend. It also puts some power (with limited danger) back into the hands of the developer. Any Config::* module could be used I use Config::Auto because it has the ability to use different formats of configration files with modifying our core module. Using plain text configuration files make it easier for installers to adjust the application to their needs and allows for reuse of the same configuration values between languages if needed. This all makes our applications decoupled because they rely on less and less language specific values.

Creating Our "Base" Class
We are going to create an initial module named Base.pm for this OO project. We use Class::Accessor within the Base module and from an application level the usage of OurApp::Base class is hidden. The Base module is for the API or object creator not the person writing applications based on it. We decide on our module name first, the one we want people to use. Lets call it OurApp So inside of OurApp.pm we do:
package OurApp; use strict; use base ( 'OurApp::Base' ); 1;
That was a lot of work :), but what did we do? We created a sub class of our Base class file. The Base package will handle all our actual class creation activities. So lets make that class now.
package OurApp::Base; use strict; use base ( 'Class::Accessor' ); use Config::Auto; sub new { my ($class,%args) = @_; my $self = {}; bless $self , $class; if ($args{conf_file}) { $self->_make_accessors(\%args); } return $self; } sub _make_accessors { my ($self,$args) = @_; my $config = Config::Auto::parse( $args->{conf_file}, format => $args->{conf_format} || 'e +qual' ); OurApp::Base->mk_accessors(keys %{$config}); foreach (keys %{$config}) { $self->$_($config->{$_}); } } 1;


Configuration File Contents
base_directory=/home/trs80 show_code=1 turn_off_sprinkler=0 feed_dog=1


Test Script
#!/usr/bin/perl use strict; use OurApp; my $object = OurApp->new( conf_file => "/home/trs80/ourapp.conf", conf_format => 'equal' ); print $object->base_directory() , "\n"; $object->base_directory("/home/otheruser/"); print $object->base_directory() , "\n";
What This Does for Us
Now if we run this the configuration file keys will be come accessor methods with the values as assigned in the conf file, but we can also set them to new values using the accessor method.

This gets rid of a lot of typing and makes it easy to add new options to the application without hard coding and without rewriting the Base class.

Now say you want to set up a phone_number method, you simply add: phone_number=555 1212 to your configuration file. The method is automatically added to your application the next time it runs.

This is also helpful for when you know the parameters are going to change on something or you decide in the future to move something into the business logic. Your config key equals your method name so you can look up the names of methods in that file. It is also useful to set values between the development and production server.

Further Exercises/Thoughts
Add values to the configuration and you see there is no need to modify the API level modules, this allows you make your application more decoupled because you can add variable elements at the configuration/development level and developers can create new client/need defined methods so the application keeps an OO interface as much as possible.

This technique can be extended to allow for separate configuration files that can override the master configuration, that is at the end user level they may have their own configuration files that can be loaded to override the master configurations file default. If needed the _make_accessors method could be overridden in OurApp.pm to assign values based on system default or user preferences.

This technique has helped me greatly when running a code set in a development environment and then moving it to a production server.

There should be more flexibility built into the new method that allows for passing data structures that can be used for the configuration, such as a hash ref for method and defaults values as well as array or array ref to provide empty method creation. Another nice feature is to have a default/fallback configuration file specified in the _make_accessors method. Ultimately it would be nice to allow a database based configuration option that doesn't require the application developer to create all the DBI code.


Caveats
There are some hidden and not so hidden dangers to this technique. One danger is repeating a configuration variable (key) names or colliding with built in real methods, but naming conventions and guidelines can help avoid that. The technique I have adopted is to name the configuration variables conf___, which will likely not occur directly in the API at any point. Another hidden danger is that there is no way to document the accessors efficiently since they are often added by the developer and never moved into the core, but then that is reasonable to some extent since the API has served it's purpose. The end developer has the responsibility to adequately document their specific needs.

There is most likely a cost in terms of speed at some level, but that cost would vary depending on several factors:
  1. The number of configuration elements
  2. The lifespan of the objects
  3. Number of times an object needs to be created
  4. The scale of the application


Replies are listed 'Best First'.
Re: Object Oriented configuration values
by blokhead (Monsignor) on Feb 10, 2003 at 06:45 UTC
    I see a few strange things, but it could be because I don't completely understand what kind of objects these are supposed to be. Are they just an OOP wrapper API to these config files, or do they have other purposes? It seems like you're trying to have accessors defined on a per-object basis (you (re)generate the accessors every time the new method is called). But you create these accessors on a per-class basis, and all of your objects are of the same class. Do you only ever instantiate one of these OurApp objects per execution of your app?

    The way I understand it, I could write the following:

    use OurApp; my $obj1 = OurApp->new(conf_file => 'conf1'); my $obj2 = OurApp->new(conf_file => 'conf2'); # can use both foo and bar accessors on BOTH objects print $obj1->foo, $obj1->bar; print $obj2->foo, $obj2->bar; __END__ conf1 contains: foo=1 conf2 contains: bar=1
    Wouldn't you want $obj1->bar and $obj2->foo to give an error like Can't locate object method "bar" via package "OurApp"? It won't happen if the OurApp namespace has been given both methods. Of course, if you don't plan on having multiple OurApp objects around this isn't a problem.

    blokhead

Re: Object Oriented configuration values
by IlyaM (Parson) on Feb 10, 2003 at 12:55 UTC

    To my taste application object and configuration object are quite distinct things so I would not mix them. Personally I'd make application object a facade to configuration object:

    my $object = OurApp->new(...); print $object->config->base_directory, "\n";

    Here $object->config returns config object. One advantage of this method is that fixes your problem with possible method and config variables name collisions.

    It is also worth to mention that you are probably reinventing the wheel. AppConfig provides similar OO inteface to config files (i.e. config vars as methods)

    --
    Ilya Martynov, ilya@iponweb.net
    CTO IPonWEB (UK) Ltd
    Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
    Personal website - http://martynov.org

Re: Object Oriented configuration values
by Ctrl-z (Friar) on Feb 10, 2003 at 17:05 UTC
    maybe i am being dense, but whats up with?
    print $object->config(base_directory); $object->config(base_directory => "/home/blah/foo"); print $object->config(base_directory);
    Why would you need each config entry to have its own named accessor/mutator?



    time was, I could move my arms like a bird and...