如何将接口注入具体的类层次结构中

时间:2016-07-05 04:40:32

标签: php oop design-patterns interface php-7

所以,我有一个我无法修改的现有类层次结构。该层次结构中现有的类消费者不仅仅是我的代码库。我有另一个类(在一个新的,但是在外部库中),它具有不同的合同(类原型),具有相似但改进的功能。我希望为旧代码的现有使用者提供这种新功能。

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) {}

我希望确保以向后兼容的方式允许使用旧代码,同时允许使用尽可能少的分支来使用新代码,但是如何使用?

1 个答案:

答案 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:不要为这些混乱烦恼,只需修改消费合同并使用联合界面,就像你想要的那样。