繁体   English   中英

PHP:松耦合参数化工厂。 是否可以?

[英]PHP: Loosely Coupled Parameterized Factory. Is it possible?

我的需要:
我想实例化一个对象,其中新对象的类由作为工厂方法参数给出的对象确定。 同时,我希望工厂方法保持松散耦合,以便在添加新产品类时不需要更新。 看起来像下面的代码这样的参数化工厂是一个解决方案,但与产品类紧密耦合。

我在做什么:
我在以不同格式保存基本相同信息的数据对象之间进行翻译。 这些需要从一种格式转换为另一种格式,但在它们请求翻译器之前,我不知道数据对象属于哪个类。 我知道翻译器是如何实现的,但我正在努力解决如何为给定的对象选择合适的翻译器,而无需进行大量检查对象所属的类。 我想我应该使用工厂根据数据对象是instanceof的类来返回兼容的翻译器。

“有效”但紧密耦合的通用代码:

class Factory {

public static function getTranslator( object $a, object $b ): Product {
    if( ( $a instanceof ClassOne && $b instanceof ClassTwo )
        || ( $a instanceof ClassTwo && $b instanceof ClassOne )
    ) {
        return new TranslatorForClassOneAndTwo( $a, $b );
    }
    if( ( $a instanceof ClassOne && $b instanceof ClassThree )
            || ( $a instanceof ClassThree && $b instanceof ClassOne ) ) {
        return new TranslatorForClassOneAndThree( $a, $b );
    }
    if( ( $a instanceof ClassTwo && $b instanceof ClassThree )
            || ( $a instanceof ClassThree && $b instanceof ClassTwo ) ) {
        return new TranslatorForClassTwoAndThree( $a, $b );
    }
    //...and so on.
}
}

用法:

    $object_a = new CrmData();
    $object_b = new CmsData();
    $object_c = new MarketingData();

//Translate object_a to object_b 
    $translator_alpha = Factory::getTranslator($object_a , $object_b);
    $translated_object_one = $translator_alpha->translate($object_a , $object_b);

    $translator_beta = Factory::getTranslator($object_a , $object_c);
    $translated_object_two = $translator_beta->translate($object_a , $object_c);

//$translated_object_one is the data of $object_a but same class as $object_b, CmsData
//$translated_object_two is the data of $object_a but same class as $object_c, MarketingData

使用上面的代码,每次我添加一个新的Product类时,我都需要向这个工厂方法添加一个新案例。 如果有一种方法可以基于与上述相同的逻辑实例化这些产品,而无需明确定义每个案例,那将是更可取的。 似乎有一种方法可以使用一些面向对象的结构来做到这一点,但我没有想法。 此外,了解目前这在 php 中是否不可能,如果没有好的解决方案,我会坚持使用更明确的结构,这将很有帮助。

由于翻译器对于每个可翻译文件都是独一无二的,因此您可以为翻译器类制定一个简单的命名方案,并基于此加载它们。

public static function getTranslator(Translatable $a, Translatable $b): Translator
{
  $translatorNamespace = (new \ReflectionClass(BaseTranslator::class))->getNamespaceName();
  $translatorClassName = $translatorNamespace 
      . '\\TranslatorFor' . (new \ReflectionClass($a))->getShortName() 
      . 'And' . (new \ReflectionClass($b))->getShortName();
  return new $translatorClassName();
}

如果你不能严格命名你的实际翻译器类,你可以使用class_alias来创建适当的别名。

演示(带命名空间): https : //3v4l.org/KvsAL

另一种可能性(与我的其他答案不同,更接近您自己的答案):

  • Translator对象响应canTranslateBetween(Translatable, Translatable): bool (这可以在合同上定义为 PHP 中的抽象静态方法,顺便说一句),
  • Factory将一组翻译器类作为依赖项(构造函数参数),然后实例化其中正确的一个。

你的工厂类看起来像这样:

class TranslatorFactory
{
  /** @var string[] */
  private $translatorClasses;

  public function __construct(array $translatorClasses)
  {
    $this->translatorClasses = $translatorClasses;
  }

  public function createTranslator(Translatable $a, Translatable $b): Translator
  {
    foreach ($this->translatorClasses as $translatorClass) {
      if ($translatorClass::canTranslateBetween($a, $b)) {
        return new $translatorClass;
      }
    }
    throw new \RuntimeException('Could not find translator.');
  }
}

用法:

$translatorFactory = new TranslatorFactory([
  Translator1::class, 
  Translator2::class, 
  Translator3::class
]);

$translatable1 = new Translatable1();
$translatable3 = new Translatable3();
$translator = $translatorFactory->createTranslator($translatable1, $translatable3);

这具有以任何方式干净且非黑客的优点(没有“魔法”或反射或任何类似的东西)。 唯一的缺点是,必须在实例化工厂时手动列出所有翻译类,但我觉得这实际上是有道理的:毕竟它会从中选择一个。

演示: https : //3v4l.org/nfuB2

您可以将您的逻辑委托给某个数组,但您仍然没有抽象,但会使其易于配置。 第一个选项将是与$a比较的第一个类,第二个将是与$b比较的类,第三个将是返回对象实例:

<?php

class ClassOne {}
class ClassTwo {}
class ClassThree {}
class Product {}
class ProductOne extends Product {}
class ProductTwo extends Product {}
class ProductThree extends Product {}

class Factory {
    public static function getProduct(object $a, object $b): Product {
        $array = [
            ['ClassOne','ClassTwo', 'ProductOne'],
            ['ClassTwo','ClassOne', 'ProductOne'],
            ['ClassOne','ClassThree', 'ProductTwo'],
            ['ClassThree','ClassOne', 'ProductTwo'],
            ['ClassThree','ClassTwo', 'ProductTwo'],
            ['ClassTwo','ClassThree', 'ProductThree'],
        ];

        foreach($array as $classes) {
            if (get_class($a) == $classes[0] && get_class($b) == $classes[1]) {
                return new $classes[2];
            }
        }
    }
}


$a = new ClassOne;
$b = new ClassThree;

$x = Factory::getProduct($a, $b);
var_dump($x); //output object(ProductTwo)#3 (0) {}

这有点像黑客。 我认为它可以工作,但它可能是一个坏主意。 它似乎也不适用于自动加载。

首先,确保所有 Translator 子类都已加载。

然后:

    class Factory {

        public static function getTranslator( object $a, object $b ): Translator {
            foreach( get_declared_classes() as $class ) {
                if( is_subclass_of( $class, ObjectTranslator::class ) ) {
                    $translator[] = $class;
                }
            }

            foreach($translator as $child) {
                if($child::isCompatible($object_a, $object_b)) {
                    return new $child();
                }
            }
        }
    }

你觉得怎么样。 好主意? 馊主意? 在有人看到之前烧掉它? 为什么?

我最大的担忧是它隐含地要求加载所有子类,这不直观。 反射也可能会减慢速度,但这可以进行测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM