PHP:松耦合参数化工厂。可能吗?
PHP: Loosely Coupled Parameterized Factory. Is it possible?
我的需求:
我想实例化一个对象,其中新对象的 class 由作为参数提供给工厂方法的对象确定。同时我想保持工厂方法松散耦合,这样当我添加新产品时不需要更新它 class。像下面的代码这样的参数化工厂似乎是一个解决方案,但与产品紧密耦合 classes.
我在做什么:
我在以不同格式保存基本相同信息的数据对象之间进行转换。这些需要从一种格式转换为另一种格式,但在他们请求翻译器之前,我不知道 Class 数据对象属于什么。我知道翻译器是如何实现的,但我正在努力研究如何 select 给定对象的正确翻译器,而无需进行大量检查 class 该对象属于什么。我在想我应该根据 class 数据对象是 instanceof 的内容,使用工厂来 return 兼容的翻译器。
'works'但紧耦合的通用代码:
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
class 时,我都需要向该工厂方法添加一个新的案例。如果有一种方法可以根据与上述相同的逻辑实例化这些产品,而不必显式定义每个案例,那将是更可取的。似乎有一种方法可以使用一些 OO 结构来做到这一点,但我没有想法。此外,了解目前在 php 中这是否不可能,如果没有好的解决方案,我将无法使用更明确的结构,这将很有帮助。
您可以将您的逻辑委托给某个数组,但您仍然没有抽象,但会使其易于配置。
第一个选项是第一个 class 与 $a
比较,第二个是 class 与 $b
比较,第三个是 return对象实例:
<?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) {}
这有点坑爹。我认为它可以工作,但这可能是个坏主意。它似乎也不适用于自动加载。
首先,确保已加载所有翻译子 类。
然后:
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();
}
}
}
}
你怎么看。好主意?馊主意?在别人看到之前烧掉它?为什么?
我最担心的是它隐含地要求加载所有子类,这是不直观的。它也可能因反射而减慢,但可以测试。
由于翻译器是独一无二的,并且特定于每个可翻译的,您可以为您的翻译器制定一个简单的命名方案 类 并基于此加载它们。
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
将一组翻译器 classes 作为依赖项(构造函数参数),然后实例化其中正确的一个。
您的工厂 class 看起来像这样:
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);
这样做的好处是在任何方面都是干净的和非骇人听闻的(没有 "magic" 或反射或类似的东西)。唯一的缺点是,必须在实例化工厂时手动列出所有翻译 classes,但我觉得这实际上是有道理的:毕竟它会从中选择一个。
我的需求:
我想实例化一个对象,其中新对象的 class 由作为参数提供给工厂方法的对象确定。同时我想保持工厂方法松散耦合,这样当我添加新产品时不需要更新它 class。像下面的代码这样的参数化工厂似乎是一个解决方案,但与产品紧密耦合 classes.
我在做什么:
我在以不同格式保存基本相同信息的数据对象之间进行转换。这些需要从一种格式转换为另一种格式,但在他们请求翻译器之前,我不知道 Class 数据对象属于什么。我知道翻译器是如何实现的,但我正在努力研究如何 select 给定对象的正确翻译器,而无需进行大量检查 class 该对象属于什么。我在想我应该根据 class 数据对象是 instanceof 的内容,使用工厂来 return 兼容的翻译器。
'works'但紧耦合的通用代码:
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
class 时,我都需要向该工厂方法添加一个新的案例。如果有一种方法可以根据与上述相同的逻辑实例化这些产品,而不必显式定义每个案例,那将是更可取的。似乎有一种方法可以使用一些 OO 结构来做到这一点,但我没有想法。此外,了解目前在 php 中这是否不可能,如果没有好的解决方案,我将无法使用更明确的结构,这将很有帮助。
您可以将您的逻辑委托给某个数组,但您仍然没有抽象,但会使其易于配置。
第一个选项是第一个 class 与 $a
比较,第二个是 class 与 $b
比较,第三个是 return对象实例:
<?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) {}
这有点坑爹。我认为它可以工作,但这可能是个坏主意。它似乎也不适用于自动加载。
首先,确保已加载所有翻译子 类。
然后:
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();
}
}
}
}
你怎么看。好主意?馊主意?在别人看到之前烧掉它?为什么?
我最担心的是它隐含地要求加载所有子类,这是不直观的。它也可能因反射而减慢,但可以测试。
由于翻译器是独一无二的,并且特定于每个可翻译的,您可以为您的翻译器制定一个简单的命名方案 类 并基于此加载它们。
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
将一组翻译器 classes 作为依赖项(构造函数参数),然后实例化其中正确的一个。
您的工厂 class 看起来像这样:
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);
这样做的好处是在任何方面都是干净的和非骇人听闻的(没有 "magic" 或反射或类似的东西)。唯一的缺点是,必须在实例化工厂时手动列出所有翻译 classes,但我觉得这实际上是有道理的:毕竟它会从中选择一个。