创建使用其他工厂构建最终具体对象的工厂实现是一种好习惯吗?

In an application I'm building there's a CLI entry point class:

class CLIEntryPoint {

   protected $factory;

   public function __construct(ApplicationObjectFactoryInterface $factory) {
      $this->factory = $factory;
   }

   public function run(...$args) {
      $choice = $args[1];
      $appObject = $this->factory->makeApplicationObject($choice);
      $appObject->doApplicationRelatedStuff();
   }
}

This entry point is created using Dependency Injection in my "front controller" script and it receives an ApplicationObjectFactoryInterface implementation (actually the current implementation of ApplicationObjectFactoryInterface is injected by the DI container, which in turn reads it from its configuration file, but that's not the point).

The current implementation of ApplicationObjectFactoryInterface also uses DI and depends on other factories which help it building the resulting application object:

class CurrentImplementationOfApplicationObjectFactory implements ApplicationObjectFactoryInterface {

   protected $someComponentFactory;
   protected $anotherComponentFactory;

   public function __construct(SomeComponentFactoryInterface $someComponentFactory, AnotherComponentFactoryInterface $anotherComponentFactory) {
      $this->someComponentFactory = $someComponentFactory;
      $this->anotherComponentFactory = $anotherComponentFactory;
   }

   /**
   * Interface's method
   * 
   * @return ApplicationObjectInterface
   */
   public function makeApplicationObject($choice) {
      $component = $this->someComponentFactory->makeSomeComponent();
      $anotherComponent = $this->anotherComponent->makeAnotherComponent();
      switch ($choice) {
         case 1:
            return new CurrentImplementationOfApplicationObject1($component, $anotherComponent);
         case 2:
            return new CurrentImplementationOfApplicationObject2($component, $anotherComponent);
         default:
            return new DefaultImplementationOfApplicationObject($component, $anotherComponent);
      }
   }

}

Here CurrentImplementationOfApplicationObject1, CurrentImplementationOfApplicationObject2 and DefaultImplementationOfApplicationObject all implement the ApplicationObjectInterface interface and therefore they all have the doApplicationRelatedStuff method.

I would like to know whether it's good practice or not to write code like I did and if not how can I improve it.

Basically here I am creating a component which depends on other components in order to function properly using a factory which in turn needs inner factories to build the component which implements the ApplicationObjectInterface interface.

Is it considered good practice?

Thanks for the attention, as always!

EDIT: I looked at the article of Steven and tried to refactor CLIEntryPoint. The only problem now seems to be how to pass the $choice parameter to the factory which now is inside of the proxy when the run() method is called. Is this code structure better than the one I posted above? Of course, SomeComponentFactoryInterface and AnotherComponentFactoryInterface should follow the same behaviour (the factory that uses them should not use them directly, but through two proxies which implement, in order, SomeComponentInterface and AnotherComponentInterface). I hope I get it right, anyway, here is the code:

class CLIEntryPoint {

   protected $applicationObject;

   public function __construct(ApplicationObjectInterface $applicationObject) {
      $this->applicationObject = $applicationObject;
   }

   public function run(...$args) {
      $choice = $args[1]; // How do I deal with different choices when I am using a Proxy? I should have different application objects depending on input.
      $this->applicationObject->doApplicationRelatedStuff();
   }
}

interface ApplicationObjectInterface {

    public function doApplicationRelatedStuff();

}

class ApplicationObjectProxy implements ApplicationObjectInterface {

    protected $applicationObjectFactory;
    protected $applicationObjectImplementation = NULL;

    public function __construct(ApplicationObjectFactoryInterface $factory) {
        $this->applicationObjectFactory = $factory;
    }

    public function __call($method, $args) {
        // Calling interface's
        $implementation = $this->getImplementation();
        $methodOfInterfaceToCall = preg_replace('/Proxy$/', '', $method);
        return $implementation->{$methodOfInterfaceToCall}(...$args);
    }

    /**
     * Laxy loading method.
     */
    protected function getImplementation() {
        if (is_null($this->applicationObjectImplementation)) {
            $this->applicationObjectImplementation = $this->applicationObjectFactory->makeApplicationObject(); // Choice should go here somehow...
        }
        return $this->applicationObjectImplementation;
    }

    public function doApplicationRelatedStuff() {
        // This will call the PHP's magic `__call` method, which in turn will forward the call to the application object's
        // implementation returned by the factory.
        return $this->doApplicationRelatedStuffProxy();
    }

}

Actually yes, this is a pattern called the Abstract Factory Pattern. So an example that I used to present it in front of my class during my undergrad:

So if you are building a video game first person shooter, you might want to create three concrete factories like:

  1. FlyingMonsterFactory
  2. SwimmingMonsterFactory
  3. WalkingMonsterFactory.

All these factories would implement an abstract MonsterFactory.

With this, you can have your video game create a level in which you want waves of the same type of monsters, so you can have a randomWaveMonsterGenerator method return a MonsterFactory which might have returned a concrete SwimmingMonsterFactory. So then you will have a wave of SwimmingMonster(s) generated by the SwimmingMonsterFactory.


So answer your description more directly, looking at your code above, you asked the question on choice for Dependency Injection. With Dependency Injection, I believe for this type of pattern, you will have to inject every concrete class before your code even attempts to get the implementation class.

So for example:

  • Your code above says the run method gives an argument called choice.
  • With this choice, you will have to use it as a parameter into a getImplementation method.
  • All the concrete objects that the getImplementation method that rely upon Dependency Injection have to be created BEFORE you call the getImplementation method.
  • But since you don't know which implementation class will be called, I believe you have to inject ALL the implementation classes before hand.
  • Then you can use the choice variable as a parameter to get the correct implemented factory class.

Hope this helps!