Explosion Pills
Explosion Pills

Reputation: 191779

Avoiding conditionals in lazy loading

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

Answers (6)

goat
goat

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

Val
Val

Reputation: 11107

Method1

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.

Method2

Java on-demand initialization by exploiting the lazy class loading.

Bottom line

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

Esailija
Esailija

Reputation: 140228

return ( $this->barn = $this->barn ? $this->barn : getBarn() );

or the php 5.3 (?) one:

return ( $this->barn = $this->barn ?: getBarn() );

Upvotes: 1

Tim
Tim

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

Jonathan Rich
Jonathan Rich

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

Lightness Races in Orbit
Lightness Races in Orbit

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

Related Questions