4

Possible Duplicate:
Dependecy Hell - how does one pass dependencies to deeply nested objects

Lately I've been struggling with this particular problem. For testing and managing reasons I decided it would be a better option to inject an object like $config to those who need it. While at start it was ok, later it started polluting the code. For example: Object A uses object B to do its job, object B uses strategy object C, object C uses object D, which needs $config object. So, I have to keep passing $config down this whole chain

In my code I have two objects like that to pass through, which makes constructors big, having duplicated code and generally it smells wrong. I would appreciate any help in refactoring this relationship.

Community
  • 1
  • 1
Boris Mikhaylov
  • 319
  • 1
  • 11
  • @stereofrog OP said "I have to keep passing $config down this whole chain", which is pretty much what the title of the linked question asks too and the (my) accepted answer in it answers. – Gordon Sep 27 '11 at 11:48

3 Answers3

2

Instead of (pseudo code as general advice) ...

config <-- ...

A.constructor (config) {
   this.foo = config.foo
   this.bar = config.bar
   this.objectB = createB (config)
}

B.constructor (config) {
   this.frob = config.frob
   this.objectC = createC (config)
}

C.constructor (config) {
   this.frobnicate = config.frobnicate
   this.objectD = createC (configD)
}

you should only pass what is really needed:

config <-- ...

A.constructor (foo, bar, frob, frobnicate) {
   this.foo = foo
   this.bar = bar
   this.objectB = createB (frob, frobnicate)
}

B.constructor (frob, frobnicate) {
   this.frob = frob
   this.objectC = createC (frobnicate)
}

C.constructor (frobnicate) {
   this.frobnicate = frobnicate
}

Have your state as local as possible. Global state is the root of an indefinite amount of debugging horror scenarios (as I smell you've just faced).

Alternatively, many classes don't have to know how their objects look like, they are just interested in the public interface. You can apply dependency inversion, then:

config <-- ...
objectC = createC (config.frobnicate)
objectB = createB (config.frob, objectC)
objectA = createA (config.foo, config.bar, objectB)

Using dependency inversion means freeing your classes from needing to know too much. E.g., a Car does not need to know about Trailer and its composition, it just needs to know about CouplingDevice:

trailer        = createTrailer (...)
couplingDevice = createCouplingDevice (...)

car.install (couplingDevice)

couplingDevice.attach (trailer)
Sebastian Mach
  • 36,158
  • 4
  • 87
  • 126
  • Thanks for your comment! But the idea is to have $config properties encapsulated. I don't want Object A to know what Object D needs, let alone I don't want client to know what every object behind object A needs. Object D decides on its own which parameters to pull from $config. What if Object D is a strategy, that could be substituted with object E, that in turn uses different $config properties? – Boris Mikhaylov Sep 27 '11 at 10:56
  • 2
    unless the above pseudo code represents factories or builders, collaborators should not create their own dependencies. also, doing work in the ctor is a code smell. – Gordon Sep 27 '11 at 10:56
  • @Gordon: How don't you do work in the constructors? Having a strong C++ RAII background, I'd state the opposite (except you use some kind of dependency inversion, of course) – Sebastian Mach Sep 27 '11 at 12:10
  • see http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work for detailed explanation as to what constitutes *work*. And yes, DI and factories/builders is the suggested solution in case that's tldr. – Gordon Sep 27 '11 at 12:12
  • @Gordon: Jumped over that article. Seems to me like it states not to invest work on collaborators, and as I see it, does not state that work is a flaw in general. E.g., it would not be good design to impose memory maps and file-system functions on users of File-objects. And to fail soon and loudy, the adequate place for File-object-construction would be the constructor; a defect File is not a usable File, it should not exist, and to not exist is to not construct at all. But I agree when we filter this argument on collaborators. – Sebastian Mach Sep 27 '11 at 12:25
0

Am I right in thinking that $config contains... well, configuration information that is required by a large portion of your application? If so, it sounds like you should consider the (ubiquitious) singleton design pattern.

If you're not already familiar with it, it is a technique which allows only one instance of a class throughout the run-time of your application. This is very useful when maintaining application-wide values, since you do not run the risk of instantiating a second object; nor are you passing around 'copies' of objects.

As an example, examine the following:

<?php

class Test {

  private static $instance;

  private function __construct() {  } // Private constructor

  static function instance() {
    if (!isset(self::$instance)) {
       self::$instance = new self();
    }
    return self::$instance;
  }
}

$a = Test::instance();
$b = Test::instance();

$a->property = 'abc';

var_dump($a->property);
var_dump($b->property);

?>

You will see that both 'instances' contain the 'property' with value 'abc' since they are both actually the same instance. I apologize if you are already familiar with this technique, but it certainly sounds like the thing you are looking for!

Edit

As pointed out below, this can still be cloned. If you really wanted to prevent this happening, you would have to override the magic method __clone() to stop this happening. The serialization observation is just being pedantic, though.

pb149
  • 2,218
  • 1
  • 20
  • 30
  • 1
    -1. This is not a UseCase for a Singleton. A config item does not have to be unique and provide a global access point. Using a Singleton here is simply refusing to think about a proper design. Doing it this way leads to hard to maintain, hard to test, hard to reuse and fragile code. See http://stackoverflow.com/questions/4595964/who-needs-singletons/4596323#4596323 – Gordon Sep 27 '11 at 11:02
  • Thanks for you answer! As to to Singleton, that is the only workaround I see for now, but it rather seems like a hack to me. As I said in my real scenario I have more than one object like that that are needed way deep into the nested scope, $config is just one of them. That's why I am looking for a more general solution – Boris Mikhaylov Sep 27 '11 at 11:04
  • @Gordon - In many cases, though, a configuration object IS going to be unique. The OP may not have said as much, but they certainly said nothing contrary to that assumption. – pb149 Sep 27 '11 at 11:06
  • 1
    And that is exactly why it *doesnt* have to be unique. The OP may only *need* just one config, but that doesnt imply the OP *may not have* more than one. And in that case, tts pointless to enforce uniqueness here when you can just [create once and only once.](http://butunclebob.com/ArticleS.UncleBob.SingletonVsJustCreateOne) – Gordon Sep 27 '11 at 11:07
  • 1
    On a sidenote, your Singleton is not a Singleton because it can be cloned and serialized thus allowing multiple instances of it. – Gordon Sep 27 '11 at 11:24
  • @Gordon True, I said nothing of `__clone()`, and have nonetheless amended my post accordingly. In most cases, though, the fact that a singleton can be serialized and thus instantiated twice is not of huge *practical* consideration. At least, I've not once encountered this actually being done, accidentally or otherwise! – pb149 Sep 27 '11 at 11:32
  • Why is that pedantic? Do you want "unique" or do you want "maybe unique"? IMO, if you already decided that the problems a Singleton brings are worth the enforced uniqueness and global access point, you should see to it that it does what it promises. And it's not like it's much effort to add a `__sleep()` or `__wakeup()` method. – Gordon Sep 27 '11 at 11:56
0

It looks like you need to use a singleton or a registry patterns.

The singleton consist of a class (with private constructor) that can be created by a static method in order to obtain the same object (forgive me for the simplification) every time you need it.

It follow this scheme:

class Config {
    static private instance=null;

    private function __constructor() {
      // do your initializzation here
    }

    static public function getInstance() {
      if (self::instance==null) {
        self::instance== new Config();
      }
      return self::instance;
    }

    // other methods and properties as needed

}

In this way you can obtain the desired object where you need it with something like

$config = Config::getInstance();

without passing it down in your call stack without resorting to globals variables.

The registry has a similar working scheme but let you create a sort of registry, hence the name, of objects you need to make available.

Eineki
  • 14,008
  • 6
  • 47
  • 54
  • 1
    Doing `Config::getInstance();` is effectively the same as resorting to global variables. You assume the class exist in the global scope and rely on the data pulled out of it to be actually in it. If you do that, you can just as well use `global $config`. No difference. – Gordon Sep 27 '11 at 12:05