所以,我有一个我无法修改的现有类层次结构。该层次结构中现有的类消费者不仅仅是我的代码库。我有另一个类(在一个新的,但是在外部库中),它具有不同的合同(类原型),具有相似但改进的功能。我希望为旧代码的现有使用者提供这种新功能。
class OldBase {}
class OldSubClass extends OldBase{}
class NewCode {}
//consumers
existingMethod(OldSubClass $c) {}
alsoExistingMethod(OldBase $c) {}
我想过使用AdapterInterface
,但这看起来似乎不太优雅。
interface NewCodeAdapterInterface
{
//interface that mimics the contract from OldBase
}
class NewCodeImplementation implements NewCodeAdapterInterface{}
//now this code can not be used with any existing OldBase objects :-\
existingMethod(NewCodeAdapterInterface $c) {}
我希望确保以向后兼容的方式允许使用旧代码,同时允许使用尽可能少的分支来使用新代码,但是如何使用?
答案 0 :(得分:0)
从您希望实现现有代码消耗的不同类的统一替换的前提开始,而不修改现有的消费者,然后我有一个......"解决方案&#34 ;
以下是当前问题的一个示例:
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);
您正在尝试找到一个允许类似的解决方案,而无需在消费者中修改任何内容:
$con = new Consumer();
$c = new C();
$con->runTestA($c);
$con->runTestB($c);
我会非常建议不要做我即将概述的内容。最好修改Consumer中的方法签名,以允许传递具有联合功能的新类。但是,我会按照要求回答这个问题......
首先,我们需要一些可以传递任何现有方法签名的类。我将使用特征来定义联合功能。
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);
}
}
此模式切换器类维护当前模式类,该类通过gets和calls。设置和取消设置都应用于这两个类,因此修改后的任何属性都不会在模式切换时丢失。
现在,如果我们可以修改消费者的消费者,我们可以组合一个自动在模式之间切换的翻译层,以找到正确的模式。
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的使用情况进行任何重大更改,因为Translator基本上是一个直通包装器。
它的工作原理是捕获由签名不匹配引发的TypeError,切换到配对类,然后再次尝试。
这是......不建议实际实施。然而,声明的约束提供了一个有趣的谜题,所以,我们在这里。
TL; DR:不要为这些混乱烦恼,只需修改消费合同并使用联合界面,就像你想要的那样。