从你想实现对现有代码所消耗的不同类的统一替换的前提出发,不修改现有消费者,那么我有一个……“解决方案”。
这是当前问题的一个示例:
class A
{
public function test()
{
echo "A\n";
}
}
class B
{
public function test()
{
echo "B\n";
}
}
class Consumer
{
public function runTestA(A $a)
{
$a->test();
}
public function runTestB(B $b)
{
$b->test();
}
}
$con = new Consumer();
$a = new A();
$b = new B();
$con->runTestA($a);
$con->runTestB($b);
您正在尝试找到一种解决方案,该解决方案将允许类似的内容,而无需修改 Consumer 中的任何内容:
$con = new Consumer();
$c = new C();
$con->runTestA($c);
$con->runTestB($c);
我强烈建议不要做我将要概述的事情。最好修改 Consumer 中的方法签名以允许传递具有联合功能的新类。但是,我将按要求回答这个问题......
首先,我们需要几个可以传递任何现有方法签名的类。我将使用 trait 来定义关节功能。
trait ExtensionTrait
{
public function test()
{
echo "New Functionality\n";
}
}
class ExtendedA extends A
{
use ExtensionTrait;
}
class ExtendedB extends B
{
use ExtensionTrait;
}
现在我们有了一些具有新功能的类,它们可以通过方法检查……如果我们通过了正确的方法。那么,我们该怎么做呢?
让我们首先组合一个快速实用程序类,它可以在两个类之间轻松切换。
class ModeSwitcher
{
private $a;
private $b;
public $mode;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->mode = $this->a;
}
public function switchMode()
{
if ($this->mode instanceof ExtendedA)
{
$this->mode = $this->b;
}
elseif ($this->mode instanceof ExtendedB)
{
$this->mode = $this->a;
}
}
public function __set($name, $value)
{
$this->a->$name = $value;
$this->b->$name = $value;
}
public function __isset($name)
{
return isset($this->mode->$name);
}
public function __unset($name)
{
unset($this->a->$name);
unset($this->b->$name);
}
public function __call($meth, $args)
{
return call_user_func_array([$this->mode, $meth], $args);
}
}
这个模式切换器类维护一个当前模式类,它通过获取和调用。设置和取消设置适用于这两个类,因此任何修改的属性都不会在模式切换时丢失。
现在,如果我们可以修改消费者的消费者,我们可以组合一个翻译层,自动在模式之间切换以找到正确的模式。
class ConsumerTranslator
{
private $consumer;
public function __construct(Consumer $consumer)
{
$this->consumer = $consumer;
}
public function __get($name)
{
return $this->consumer->$name;
}
public function __set($name, $value)
{
$this->consumer->$name = $value;
}
public function __isset($name)
{
return isset($this->consumer->$name);
}
public function __unset($name)
{
unset($this->consumer->$name);
}
public function __call($methName, $arguments)
{
try
{
$tempArgs = $arguments;
foreach ($tempArgs as $i => $arg)
{
if ($arg instanceof ModeSwitcher)
{
$tempArgs[$i] = $arg->mode;
}
}
return call_user_func_array([$this->consumer, $methName], $tempArgs);
}
catch (\TypeError $e)
{
$tempArgs = $arguments;
foreach ($tempArgs as $i => $arg)
{
if ($arg instanceof ModeSwitcher)
{
$arg->switchMode();
$tempArgs[$i] = $arg->mode;
}
}
return call_user_func_array([$this->consumer, $methName], $tempArgs);
}
}
}
然后,我们可以像这样使用组合功能:
$con = new Consumer();
$t = new ConsumerTranslator($con);
$a = new ExtendedA();
$b = new ExtendedB();
$m = new ModeSwitcher($a, $b);
$t->runTestA($m);
$t->runTestB($m);
这使您可以互换使用任一类树,而无需对 Consumer 进行任何修改,也无需对 Consumer 的使用配置文件进行任何重大更改,因为 Translator 基本上是一个传递包装器。
它的工作原理是捕获签名不匹配引发的 TypeError,切换到配对类,然后重试。
这...不建议实际实施。不过,声明的约束提供了一个有趣的谜题,所以,我们来了。
TL;DR:不要理会这些乱七八糟的东西,只需修改消费合约并使用联合接口,就像你打算的那样。