[英]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.