Re: Log::Log4perl and singleton design issue
by Joost (Canon) on Dec 19, 2004 at 16:34 UTC
|
If your code is structured in packages, you would probably want a logger object per package or class. I'd probably do it something like this for each package:
package Some::Package;
our $Log = Log::Log4perl->get_logger(__PACKAGE__);
Or you could wrap this in a class method, so you can inherit it:
sub Log {
my $class = shift;
return Log::Log4perl->get_logger( ref($class) || $class);
}
It's probably not good design (and redundant) to explicitly write get_logger("some.logger") everywhere you want to log something - it makes it difficult to change the logger name, for one. If you have a reasonably well structured program, one logger object per package / class is probably enough anyway.
| [reply] [d/l] [select] |
Re: Log::Log4perl and singleton design issue
by BrowserUk (Patriarch) on Dec 19, 2004 at 16:41 UTC
|
(IMO)This is a case of "a spade is a spade", no matter that you might call it a "Universal, Manually-Operated, Soil Displacement & Replacement Implement".
A global is a global, and wrapping it in a 'singleton pattern' and having to make a long-winded, slow and repetitious call to get your hands on it each time you need to use it is pointless.
All you're doing is indirecting the access to the global you need, through another pair of globals, the bareword: Log::Log4perl and the string "logger_name", that you don't need. Or at least, don't need to hard-code all over your codebase.
To my way of thinking, this is OO for OO's sake and of no practical value.
"But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
"Think for yourself!" - Abigail
"Time is a poor substitute for thought"--theorbtwo
"Efficiency is intelligent laziness." -David Dunham
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
| [reply] [d/l] [select] |
|
|
"No practical value"!? The value of a singleton is in where it's used. In this case, it may seem like less value, but if you wanted to switch from a singleton to a pool structure, the code change is nil.
Take for instance an object that used semaphores and locking to prevent multiple people exuting at once. For instance, a banks total available funds, not per account basis. One person would want to withdraw $50, but you don't want to create race conditions.
If taking out money starts to create contention, you can create pools of bank totals so that multiple people can withdraw and deposit at the same time without race conditions, and less contention. If $5 million were broken out into pools of $1 million, 5 people could withdraw and deposit at the same time w/o creating a race condition provided each person got a different object, one for each of the million. As pooled items aren't needed, they can be put away and dealocated.
Where does this all lead up to? If you use the singleton pattern, everyone would use the single, lockable object. If locking becomes a problem, then you can change the code in whatever creates your singleton instances to create a limit of 5 pooled objects. If all 5 are in use, you have to wait. if 4 are in use, you get a new one. Basically, a pool, as you and I know of. Using the pattern will result in less code change. If you use a global, you can't accomplish this. If you are using a singleton, it's a lot easier. In some OO languages, this is very easy to implement. In others, it takes foot work. But by far, it is not useless.
----
Give me strength for today..
I will not talk it away..
Just for a moment..
It will burn through the clouds..
and shine down on me.
| [reply] |
|
|
There is no reason that the global in question cannot be an object. And no reason that object shouldn't actually represent a pool of things, performing locking, or whatever else you might require under the covers.
Global is about scope, not encapsulation. If the resource has application wide usage, there is no reason that it should not have application wide scope. That the resource is an object (or represented by an object) is neither here nor there.
Once you accept that an object that has application wide scope, is just a.n.other object with global scope. It's just another object that gets instantiated early in the program and lives for the life of the program. How it is implemented, a single thing, or a pool of things is irrelevant.
The 'singleton pattern' makes a special case where no special case is needed.
Of course, if the needs are simple, then I'd probably use a glob for logging, and if the needs became more complex, then I'd tie the glob.
"But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
"Think for yourself!" - Abigail
"Time is a poor substitute for thought"--theorbtwo
"Efficiency is intelligent laziness." -David Dunham
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
| [reply] |
|
|
|
|
|
|
|
A global is a global, and wrapping it in a 'singleton pattern' and having to make a long-winded, slow and repetitious call to get your hands on it each time you need to use it is pointless.
Well, you don't have to make the call everytime - you can just call once for each logger you need and cache the result. The fact that get_logger($logname) always returns the same object for the same logname is just a convenience. It has very little to do with object oriented programming (except maybe for the terminology).
| [reply] |
|
|
The practical value is the interface which defines how you log things.
If you use a global variable, then everywhere you want to log something you have to use that particular global variable. You've just tightly coupled all that code. If you decide to change how things work, you have to change a lot of code. With a method call, you don't have to know ahead of time what's you're going to get or how it's implemented. All of that stuff is hidden by the method call. Once you separate yourself from the implementation, you can change it without having to go through the rest of your code to maintain the tight coupling.
You still want to pass around references because you want every thing working with the same copy of things, and any changes apply to every thing that uses it. Since you want a reference, it's not too much of a leap to use one that's been blessed.
You don't need a slow or repititous call either. You make your own package that represents the logger. Behind that is all the gory details. When you decide to switch from L4p to something else, your code doesn't need to know that. The class name and the name of the logger aren't global variables: they are symbolic constants. They represent things you hard-coded (or determined) somewhere else so you don't have to know what it is ahead of time. Behind that package you figure out how to implement it, and are free to change the decision without rippling changes through the code base.
my $log = Log->new( SYMBOLIC_CONSTANT );
The Singleton is a way to control access without giving people direct access. When I use them, the actual object is a lexical variable in its package. Anyone who wants a reference to it has to go through the interface. They can't futz with package names. They get their reference to it, store it in whichever variable they like, and get on with life. You don't get that control with package variables.
--
brian d foy <bdfoy@cpan.org>
| [reply] [d/l] |
|
|
I made no critisism of Log4Perl. My expressed opinion was limited soley to the "singleton pattern".
The OP gave reference
Anywhere I need to log something, I call get_logger() and then call my methods, typically without a temporary variable in the style of Log::Log4perl->get_logger("logger_name")->log("message").
In that, he is hard coding two invarients all over his codebase: "Log::Log4perl" & "logger_name". By doing so, he is creating a maintainance problem.
One solution is to confine those invarients to a single place by making the call once at the top level of his application and sharing the log object returned with the deeper levels of code via a global. Another is to hide the invarients behind a package global in the form of a function name. Both perfectly practical, sensible solutions.
The thing I noted is that either of these will work just as well with a standard object (class), or even a (tied) glob and the "singleton pattern' has achieved nothing.
That is because the solutions offered override the 'singleton pattern', whose sole purpose is to avoid sharing through globals (even if disguised behind a function interface). In order to be using the 'singleton pattern' as it is intended, it is necessary to re-aquire access to the singleton resource, via it's interface, at each point (or scope) in the application. Each class in the application should instantiate it's own instance of the logger either on-demand, or as a instance (or class) variable (or method).
Following the 'singleton pattern' therefore requires embedding the invarients in multiple places. Either within the constructors of all the classes that will use the resource, or in the parameters to those constructors.
You've suggested that this embedding of invarients in multiple places can be avoided by using a symbolic constant, which I agree is another perfectly practical solution, but all you have done is to move the invarient behind a different label--which also has global scope.
A rose by any other name...
I also suggest that all these solutions work equally well if the class of the logger object is any normal class that, given the same values to it's constructor returns the same instance. The 'singleton pattern' servers only to make a special case of something that doesn't need a special case.
It is also the case that all the various ways of allowing code to be written to share a single resource, without hardcoding the name of that resource all over the codebase, could equally return a glob. And by returning a glob rather than an object, it would allow the users of the logger to use all the standard Perl tools available for formatting, and print(f)ing to globs, rather than having to reinvent these in each module, or rely on the logging object to provide methods for access to all of them.
It would also allow programs that start off logging to a file they opened in the normal way, to 'upgrade' to using the logger, by simply tieing the glob to the logging object, instead of opening the file. So retroactively gaining access to all the features of the logger without having to recode each individual line of logging to use an alternative interface.
"But you should never overestimate the ingenuity of the sceptics to come up with a counter-argument." -Myles Allen
"Think for yourself!" - Abigail
"Time is a poor substitute for thought"--theorbtwo
"Efficiency is intelligent laziness." -David Dunham
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
| [reply] [d/l] [select] |
Re: Log::Log4perl and singleton design issue
by johnnywang (Priest) on Dec 19, 2004 at 21:04 UTC
|
I have a related un-comfortable feeling when using L4p. I use log4j for my java projects, same feeling there. When coding my packages/objects, I always use a package-wide variable, as suggested by joost in his post above (Re: Log::Log4perl and singleton design issue). I agree with him that seems to be the right thing, and works fine for me. My problem is that whenever I write a script that uses those packages, I need to remember to always call Log::Log4perl->init("file.conf"). This drives me crazy since I can't easily write a commandline one-liner, or a simple test script. I've run into this numerous times, have to figure out where the file.conf is, etc.. Is there a way to avoid this? I'm not even sure what a correct solution is, but if I constantly run into this, something is not right. | [reply] |
|
|
I don't know what cool features of L4P you would be losing if you do this, so someone beat me with a cluestick if needed.
Whenever I want to use a public module but I don't like it's semantics, I write a module to change it to be more friendly. Sometimes this is making a non-OO module behave in an OO fashion, sometimes it is merely changing the API of the object involved, and in a few cases it is turning an OO module into a non OO module. I think this is what you want here.
sub log_it {
Log::Log4Perl->logger('section')
->log(shift);
}
For instance, if you had a module that exported log_it, you could make sure it calls Log::Log4Perl->init('file.conf') (in BEGIN maybe?) and so make sure that it is always called. Boom, you get your oneliners back.
Plus, you also get shorter call symantics - log_it('my_log_message') is a lot friendlier then Log::Log4Perl->logger('section')->log('my_log_message), but then of course it is the code that is actually run by log_it and so does the same thing with less typing. | [reply] [d/l] [select] |
Re: Log::Log4perl and singleton design issue
by sasikumar (Monk) on Dec 20, 2004 at 06:40 UTC
|
Hi
When OO emrges in our programs it demands not to use the
globar variables. But there are lot of application that
would demand us the global memory. The solution to this is
not avoiding the singleton pattern but its in how do go
about in hadling these singleten objects.
One way to do this is have a small package and inherit all other packages of your application from this package. This package can be the base class of your whole application. This is how we solved this issue in our old company. But still the questions remains....
Performance or System design....??
As far as i am concerned one of these things has to be
scaped for the need of other if our application demands.
After all OO is for us to make our application look
simple.It all depends on the way we need our system to
behave.
Thanks
Sasi Kumar
| [reply] |