Reputation: 191779
Just to clarify, I mean something like:
class foon {
private $barn = null;
public function getBarn() {
if (is_null($this->barn)) {
$this->barn = getBarnImpl();
}
return $this->barn;
}
}
This is especially nice when you don't always need getBarn
, and getBarn
is particularly expensive (e.g. has a DB call). Is there any way to avoid the conditional? This takes up a lot of space, looks ugly, and seeing conditionals disappear is always nice. Is there some other paradigm to handle this lazy loading that I just can't see?
Upvotes: 7
Views: 269
Reputation: 31823
By using php's __call()
magic method, we can easily write a decorator object that intercepts all method calls, and caches the return values.
One time I did something like this:
class MethodReturnValueCache {
protected $vals = array();
protected $obj;
function __construct($obj) {
$this->obj = $obj;
}
function __call($meth, $args) {
if (!array_key_exists($meth, $this->vals)) {
$this->vals[$meth] = call_user_func_array(array($this->obj, $meth), $args);
}
return $this->vals[$meth];
}
}
then
$cachedFoon = new MethodReturnValueCache(new foon);
$cachedFoon->getBarn();
Upvotes: 2
Reputation: 11107
I can think of listener class.
Constructor () {
object = null
listener = new Object() { // this is called once
object = init()
listener = new Object() { // next time
do-nothing() // this is called
}
}
Object get() {
listener.invoke()
return object
This has no condition checkers but it adds an extra field to every object, effectively duplicaing the memory consumption whereas the stupid penalty of calling useless code, listener.invoke()
, persists. I do not know how to remove it with all the polymorphysm. Because the get()
method is shared by all instances of the class, it cannot be morphed.
Java on-demand initialization by exploiting the lazy class loading.
So, it looks like the alternatives are worse than the conditional because modern CPUs optimize the branch predictions. So, check penalty will be very tiny, I expect, once code is initialized and branch is always go into one direction. The false branch will be taken only once, at the initialization time, and it will also be short compared to your initialization time. Otherwise you may be do not want to defer the initialization.
Upvotes: 1
Reputation: 140228
return ( $this->barn = $this->barn ? $this->barn : getBarn() );
or the php 5.3 (?) one:
return ( $this->barn = $this->barn ?: getBarn() );
Upvotes: 1
Reputation: 1011
I don't think I have ever seen a method for completely eliminating this type of lazy initialization checking, but it is interesting to think about. With a toy sample there doesn't seem to be any advantage, but in large objects you could refactor the lazy initialization behavior into either the object to be initialized or (more interestingly) some sort of generic lazy initializer pattern (I am picturing something roughly similar to a singleton). Basically unless they decide to build it in as a language construct (in which case it would still be there, only hidden) I think the best you can do is to encapsulate the code yourself.
class LazyObject
{
...
public function __construct($type, $args)
{
$this->type = $type;
...
}
public getInstance()
{
if (empty($this->instance))
$this->instance = new $this->type($args);
return $instance;
}
}
class AggregateObject
{
private $foo;
private $bar;
public function __construct()
{
$this->foo = new LazyObject('Foo');
$this->bar = new LazyObject('Bar');
}
public function getFoo()
{
return $this->foo->getInstance();
}
...
}
Upvotes: 1
Reputation: 1738
You could do:
return $this->barn != null ? $this->barn : ($this->barn = self::getBarnImpl());
But I don't see how that's any better.
Upvotes: 1
Reputation: 385274
I've wondered this from time to time, but I certainly can't think of one. Unless you want to create a single function to handle this with arrays and reflective property lookups.
Upvotes: 1