设计一个 Class 要求几个 Class 执行相同的任务

Design For A Class Asking Several Classes to Perform The Same Task

我有一个 Laravel 应用程序,我正在创建一个 class 来导入数据。数据存储在大约 15 个不同的模型中,因此我有一个 Class 来处理每个模型,因为每个模型都有自己的实现。此外,未来可能会添加更多模型。

我想在 Importer class 中有一行代码,例如 $importer->import(),然后它会占用所有 15 个实现 classes,并且调用他们的 import() 方法。

但是,通用 $importer->import() 方法将如下所示:

public function __construct(ImporterOne $one, ImporterTwo $two, ImporterThree $three, etc..)
{
   $this->importerOne = $importerOne;
   $this->importerTwo = $importerTwo;
   // etc ...
   $this->importerFifteen = $importerFifteen;
}

public function import()
{
    $this->importerOne->import();
    $this->importerTwo->importer();
    // etc...
}

这看起来不太好,因为这个 class 将有 15 个以上的依赖项,所有这些基本上都在做同样的事情。然后要更新它,我需要进入此处并添加/删除依赖项和 import() 方法调用。

所以我的解决方案是将 'registration' 导入器委托给每个实现,而不是让通用导入器 class 负责。实际上,是某种观察者。但是不是客户端将观察者附加到主题,而是每个实现都将自己附加到主题。

use Importer as GenericImporter

public class importerOne {

   public function __construct(SomeModel $model, GenericImporter $importer)
   {
      $this->importer = $importer;
      $this->importer->attach($this);
   }
}

// Do the same for each implementation

// and then the generic importer

public class Importer {

   protected $importables = [];

   public function attach($import_implementation)
   {
      array_push($importables, $import_implementation); 
   }

   public function import()
   {
      foreach($this->importables as $importable)
      {
          $importable->import();
      }
   }

}

这看起来不错而且很扎实。然而,问题是每个实现现在都在使用它们自己的 GenericImporter 实例。那么最好的方法是什么?我是否将 Generic Importer 实现为 Singleton?另外,出于我的研究目的,这是否属于某种设计模式?它看起来类似于 ObservorPattern,只是每个 Observer 都在注册自己。

几件事... 1) 请注意...我接触 PHP 已经很多年了...但是设计模式是我博士研究重点的一部分...但是...我确实建议您尽量遵循模式的命名约定(或者至少将 classes/methods 记录为它们在模式中扮演的角色。因为我觉得如果您可以更轻松地实现这一点做到了。

您实际上似乎在实施-逆向-observer pattern。传统上,Subject 对象保留一组 'are' 观察它的观察者(您将只有 1 个观察者,但该模式支持很多)。然后调用 "notifyObservers()" 循环遍历所有 Observer 并为每个 Observer 调用 'notify()' 。您似乎正试图向 'other way' 发送通知。这可能就是您获得 'GenericImporter' 的多个实例的原因。你希望 'importerOne' 到 'notify' 'importer' 但观察者模式会让你反过来做,你会希望 'importerOne' 等成为 'Subject'(s) 和 'Importer' 成为(单个)'Observer'。

这种设计可能不太符合您希望程序运行的方式,但这就是模式的设计方式。你让 "Observer" 做得太多了。 'Observer' 是一个被动的参与者,当 Observed(主题)class 调用 "notifyObservers()" 时,只会调用他们的 'update(Observable o, Object arg)' 方法。如果你这样设计,你的 "observer" class 可以非常简单......(就像下面的 Java 例子)只要你创建一个实现的基础 class 'Observable' 您的所有主题 class 扩展的功能(importerOne、importerTwo 等)。

[Observer] 实现了 'notify()' method/function。这个 'notify()' 是 Observer 在 'notified' 时调用的,而不是它想要 'notify' 另一端。这是带有详细注释的 wiki Java 示例(我将对此进行解释,因为我已经多年没有接触 PHP,并且可以更好地解释 wiki 上的 java 示例页)

import java.util.Observable;
import java.util.Scanner;

// This is the [Subject](Observed) class
class EventSource extends Observable implements Runnable {
    public void run() {
        while (true) {
            String response = new Scanner(System.in).next();
            setChanged();
            notifyObservers(response);
        }
    }
    // Notice that EventSource 'extends' [Observable], which means it has...
    // the following methods available to it also...(among others)...
    //
    // addObserver(Observer o) // adds a new Observer
    // deleteObserver(Observer o) // deletes a specific Observer
    // notifyObservers(Object arg) // Notifies all observers with some 'arg'
    //
    // Notice that 'notifyObservers(    )' is the only one called
}

import java.util.Observable;
import static java.lang.System.out;

class MyApp {
    public static void main(String[] args) {
    out.println("Enter Text >");

    // This is the [Subject](Observed class/object)
    EventSource eventSource = new EventSource();

    // here the 'Subject' is registering the observer
    eventSource.addObserver(
        // This is java 8 notation for a new anonymous class
        // This creates an instance of the [Observer](link below) Interface
        // The Observer Object only has one method...
        // update(Observable o, Object arg){}
        // The below code is the 'implementation' of the...
        // 'update(Observable o, Object arg)' method
        // 'obj' is the object that was being observed, 
        // 'arg' is the object passed into 'notifyObservers(Object)'...
        // from within the [Subject](observed) object.
        (Observable obj, Object arg) -> 
        {  
             // This code here is what is called when an Observed object 
             // calls 'notifyObservers()' (in this case only '1' observer)
             out.println("\nReceived response: " + arg);
        } 
    );
}

Observer JavaDoc

希望这会帮助您调整程序以更传统的方式使用观察者模式,并允许您在文档中注明您的身份 doing/etc。

你这样说:

the problem is that each implementation is now using their OWN instance of the GenericImporter

我认为这不是问题,因为 GenericImporter 只是您在实现中注入的依赖项。此外,您传递给 GenericImporter 始终是同一个对象 ,因为它会被 'reference'

传递

编辑

关于下面的评论,当你说:

when a dependency is resolved through the IoC Container, it isn't passing by reference. It's instantiating a new instance of that Class

这取决于你如何在 ioC 容器中进行绑定:如果你使用 instance(),以这种方式绑定 Importer:

$importer = new Importer();
$this->app->instance( Importer::class, $importer );

然后当在您的应用程序中请求 Importer 依赖项时,相同的 $importer 实例将从 ioC 容器中解析出来。

编辑结束

无论如何,我会通过添加一个界面来改进设计;像这样:

//INTERFACE FOR IMPORTABLES
interface Importable
{
    public function __construct( Model $model ); 
    public function import();
}

//IMPLEMENTATION OF IMPORTABLES
class importerOne implements Importable 
{   
   public function __construct( SomeModel $model )
   {
   }

   public function import()
   {
       //logic
   }
}

//THE IMPORTER CLASS
public class Importer 
{    
   protected $importables = [];

   //attach any Importable
   public function attach( Importable $import_implementation )
   {
      array_push( $this->importables, $import_implementation) ; 
   }

   //trigger Importable::import
   public function import()
   {
      foreach($this->importables as $importable)
      {
          $importable->import();
      }
   }   
}

如果出于某些特定原因您不想将 Importer 的依赖项传递给您的可导入项,为什么不附加来自客户端的可导入项?像这样:

$importer = new Importer();

$importer->attach( $importable_1 );
$importer->attach( $importable_2 );
//$importer->attach( $importable_n );

$importer->import();

这样进口商就不需要您将 Importer 依赖项传递给他们

根据您构建可导入项的方式,您还可以考虑构建并将所有这些项存储在一个数组中,并将该数组传递给导入器:

$importer->attachAll( $importables_array );