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

Re^2: OO, Library Configuration and Class Variables

by moot (Chaplain)
on Jun 23, 2005 at 20:59 UTC ( [id://469512]=note: print w/replies, xml ) Need Help??


in reply to Re: OO, Library Configuration and Class Variables
in thread OO, Library Configuration and Class Variables

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.

Replies are listed 'Best First'.
Re^3: OO, Library Configuration and Class Variables
by Transient (Hermit) on Jun 23, 2005 at 21:17 UTC
    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
      Hmm.. I grok this apart from this bit:
      sub init_lookup { # run config based upon type of source requested }
      In case I wasn't clear, my original query was how to do this config - it seems you're saying that Loopup::Remote (or LookupFactory, here) can run its own AppConfig->file(), but this raises the issue of passing in the filename from which Lookup::Remote can configure (as you can probably tell I'm against hard-coding values!).

      Also, unless I have a separate configuration file for 'library values' from the one for 'application values', I'd need to define both sets of variables that AppConfig will use, in all instances of AppConfig, or deal with the warnings it spits out about undefined variables. And something about separating out those config files strikes me as wrong.

      I guess I could just modify User to accept an AppConfig instance and pretend that there is a broader use for this than just a single remote_uri parameter, or deal with app-code knowing something about User's internals.

      Thanks for the assistance.

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

        You can make your application reads the configuration and store it in some place your User class knows.

        I wrote an application that has a %config all my modules can access (using Config::IniFile). I created sections for all modules and grab configuration from there.

        Config example:

        [User] lookup_uri = something
        Then your code could be like this:
        package App; our %config = (); &init; sub init { # loads your configuration file/parameters here and # assigns to %App::config } package User; sub new { my $self = {}; bless $self; $self->{lookup_uri} = $App::config{lookup_uri}; }
        You can also specify which class you want to use for Lookup in config file, and instantiate in User::new.

        Still hope it helps :)

        Unfortunately, I'm not familiar with AppConfig... I'm dealing with generic initialization/configuration modules here.

        LookupFactory is a factory for Lookup::* objects. It has an initialization parameter file (i.e. a config file) that has the parameters of how to instatiate different types of Lookup:: objects.

        Your initialization class runs at the start of your service, preparing for the first request. It starts up LookupFactory which receives the parameters that are appropriate to individual Lookup::* modules (e.g. RemoteURI).

        When the user->source() method is called, it requests (from UserSource) a source that is applicable to user. Based upon a separate config file (or a separate section of the same config file, if you so choose), it reads the appropriate user source for this app is "Lookup::Remote" (or more abstractly, just "Remote" or some such moniker).

        UserSource can request the item from LookupFactory which will instantiate a Lookup::Remote object (or whatever Lookup object is requested) if one is not already available in the "pool" of Lookup Objects. When the LookupFactory instantiates the Lookup::Remote object, it already has knowledge of the aforementioned RemoteURI from it's config file.

        This is my experience in a (granted, Java-based) production-environment application. It provided great separation of knowledge and sub-classes of generic objects from the business logic. Some see this as overkill, but I found it to be highly maintainable and separable. Please let me know if you have any more questions or if this is not clear. It is based upon the Avalon framework.
Re^3: OO, Library Configuration and Class Variables
by izut (Chaplain) on Jun 23, 2005 at 21:52 UTC
    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.

      I appreciate your response, but I think you're rather missing the point of what I'm trying to achieve. I don't want clients of the User class to even know there *is* a Lookup::Remote being used by User, much less have to specify Lookup::Remote as a source for discovery.

      The problem is how to communicate a high-level configuration value into a has-a class inside another class, not which has-a class to use.

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

        To be honest, I would do this the way jzut is suggesting. I do realise that you want to avoid letting others know where Users come from, but do you see that this is already a disconnect with the requirement to have others tell Lookup::Remote where to find Users? I think that this is already an abstract problem - concrete code can't fix an abstract problem.

        Solution #1: give the User package a list of Lookup objects, all pre-configured, which is the order that the lookup is done. The advantage here is that the User object doesn't need to change just because you add more lookup objects to the queue. Of course, in your scenario, this could be a completely useless advantage, I'll leave that judgement call to you.

        Solution #2: have Lookup::Remote be completely self-contained. This means it has to statically or dynamically figure out where to look stuff up. Either this means that it has the URI(s) hardcoded in it, or it has its very own config file/table/whatever which it uses to look up this type of thing. The problem with dynamics here is that the caller still needs to be aware of this if only to properly configure it indirectly.

        In my big project at work, since it's all one big inter-connected mess, I usually put module configurations in global config files, which are represented in perl through singleton objects, and then other objects can query the singleton (MyGlobals::fetch()->get_value('User_URI') as an example). It's actually an XML-like file where I can have subsections for different areas of the code, but still a single config file for everyone.

        In the rare occasion where I feel a module really is just a utility function that has little to nothing to do with the project (other than it's used by the project just as File::Spec or File::Copy are used by the project), the constructor to the object takes the info it needs, and the caller needs to set it up.

        However, I don't generally have multiple layers of it. ;-)

        New thought. You can still have the caller initialise the User package - the caller doesn't need to realise that the User package uses Lookup::Remote to pass in URIs. This is keeping in line with the way that CGI::Session or DBI work - you pass in something which is parsed and a subclass is dispatched to do the work. Nothing says you can't use something that isn't a subclass as a factory based on the input - nor that the input must be passed in to the constructor vs being pre-set via some package method.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://469512]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2024-04-25 15:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found