Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

OO, Library Configuration and Class Variables

by moot (Chaplain)
on Jun 23, 2005 at 19:10 UTC ( [id://469485]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings. This is not strictly a perl question, but I imagine answers will be perl-idiomatic, so it is relevant to this forum.

I have a module Lookup::Remote implementing a class with a class variable $RemoteURI that is intended to be configurable from calling code via accessors. The main piece of calling code is itself a class (User). OO principles dictate that any users of the User class need not concern themselves with the internal implementation of User, including that it uses Lookup::Remote.

The User class is not the right place to initialise the $RemoteURI variable since it has no access to configuration parameters (only the application-level code has that, via AppConfig). Likewise for the Lookup::Remote class.

Any app-level user of User has to be able to set $RemoteURI, but that obviously means the application has to know that User requires Lookup::Remote.

So my question is how should I approach allowing application code to set $RemoteURI without necessarily knowing the implentation of User, and thus use()ing Lookup::Remote itself, or passing the remote URI value to instances of User (which also breaks the boundary of what the calling code needs to know about User's implementation)?

For what it's worth, the Lookup::Remote library is just one class used by User - the calling code does not (need to) know from where the User's values are populated.

Update: feel free to point out ways this design might be broken. If you have a neater approach, so much the better!

--
Now hiring in Atlanta. /msg moot for details.

  • Comment on OO, Library Configuration and Class Variables

Replies are listed 'Best First'.
Re: OO, Library Configuration and Class Variables
by izut (Chaplain) on Jun 23, 2005 at 20:43 UTC
    You can use something like the earlier mentionated Factory pattern. The User class always need to get data from somewhere, you can pass the datasource to it.

    Untested code:

    # User doesn't mind if it is local or remote, he wants the data package Lookup; ... sub is_user_in_cache { ... } sub get_user_data { warn 'You should implement this method!'; } ... package Lookup::Remote; use base 'Lookup'; sub get_user_data { ... } ... package main; use Lookup::Remote; my $lookup = Lookup->new({ remote_uri => $uri, }); use User; # Tell User class the lookup method it will use. User->set_lookup($lookup); my $user = User->new({ id => 1234, });
    You will create interface methods for all Lookup objects. You can also create other Lookup classes, like Lookup::DBI, Lookup::CSV, etc, just implementing the interface methods. You can write a base class like Lookup, having all caching you need, and then specializing the datasources (DBI, CSV, SOAP).

    I hope this helps.

      Your answer mentions exactly what I'm trying to avoid - that the user of User knows about User's implementation:
      # izut said use Lookup::Remote; my $lookup = Lookup->new({ remote_uri => $uri, });
      This to me looks like User's use of Lookup::Remote is not hidden, and thus breaks the interface between User and code using it. In other words, any code instantiating a User does not and should not need to know that there could be a vast amount of code tucked away providing the user information (or the ultimate source of that information); merely that when $user->info() is requested, it is available.

      --
      Now hiring in Atlanta. /msg moot for details.

        package LookupFactory; use base qw( UserSource ); sub init_lookup { # run config based upon type of source requested } package UserSource; sub get { # get the type of user source (lookup, database, whatever) } package User sub new { # get a source based upon whatever the User config says to get } package App $user = User->new(); $user->info() # look ma! no config!
        I don't think it would break your model to have the user request the type of source from a config file. It's actually still not even necessary to do that, as it could select from a pre-created list of Lookup Sources.

        Code is (hopefully obviously) not tested and not intended to work
        You have to specify where the User module will load its data. You can also use something like this:
        package Lookup; sub new { # gets some config file }; sub get { warn 'You should implement this method!'; } sub set { warn 'You should implement this method!'; } package Lookup::Remote; use base 'Lookup'; sub get { ... }; sub set { ... }; package main; use User; User->init({ source => 'Lookup::Remote', }); my $user = User->new({ id => 1234, });
        Or you can even tell User constructor to check some value in main scope:
        package User; sub new { ... $self->{'source'} = $main::USER_LOOKUP_SOURCE; ... } package main; use User; our $USER_LOOKUP_SOURCE = LookupFactory->new('Remote'); my $user = User->new({ id => 1234, });
        You still have to inform the User class what class it will use to get data. I can be wrong, that's the reason I come to perlmonks :)

        Update: I think it is like DBI specs. I don't know what is going under the DBD module, but I have to inform DBI where the data is.

Re: OO, Library Configuration and Class Variables
by Transient (Hermit) on Jun 23, 2005 at 19:18 UTC
    Can you explain a little bit more about what RemoteURI is specifically for and what the User and Lookup::Remote classes do? It would help to envision how these things will be handed off between the different classes. How does the RemoteURI relate to the User?
      The $RemoteURI parameter tells Lookup::Remote which URI to request user information from, given a user-id. It performs this request via LWP. The User class encapsulates information concerning a user (surprise!).

      To avoid heavy network traffic, user data is cached locally for a time, so when $user->info is requested, the user code first tries Lookup::Local to determine if information is cached locally, and if not then uses Lookup::Remote (I realise these are less than perfect names, but they suffice).

      It's actually not separated out like that (there is just one Lookup class to perform the actual information collection), but for the purposes of discussion it might help to know that there are potentially two steps to determine user information.

      The $RemoteURI doesn't relate at all to the User, it is just where information will be pulled from - it's configurable, because one installation of the code may be configured to query server A, while another is configured for server B; and it's a class variable because all instances of the class have to hit the same server, and it seems wasteful to store this per-instance. The nature of this product means that round-robin DNS or other centralised user management is not an option.

      Some very simplified sample code is shown below, with questionable bits commented.

      use User; my $config = AppConfig->new; $config->define( remote_uri => { DEFAULT => '', ARGCOUNT => ARGCOUNT_ONE } ); # config file sets remote_uri; $config->file('/path/to/conf'); ### I'm trying to avoid having to do this. use Lookup::Remote; Lookup::Remote->remote_uri($config->remote_uri); ####### # Set user data from cache or remote as available. my $user = User->new(id => '1234')->init(); # Do stuff with $user..

      --
      Now hiring in Atlanta. /msg moot for details.

        It sounds as if the real issue has to do with the main app. Are you desiging both a front-end and an initial startup in the same file? That may be where the confusion comes in.

        This is very similar to what web application servers must doing during startup on a database, for instance, passing in a password for initial database startup. An application initialization script reads the config files and passes information to appropriate objects, which (usually) create and maintain pools of themselves, passing them to requesting objects. Avalon (in Java) has this kind of approach, and it's a good separation of concerns. Your front end app would simply request a User from a Factory, how it got populated (as you said), is none of it's concern.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://469485]
Approved by jbrugger
Front-paged by tlm
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (5)
As of 2024-04-19 23:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found