【问题标题】:How to inject an interface into a concrete class hierarchy如何将接口注入到具体的类层次结构中
【发布时间】:2016-11-06 19:25:36
【问题描述】:

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

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

我想确保一种向后兼容的方式,以允许使用旧代码,同时允许以一种干净的方式使用新代码,并尽可能减少后果,但是如何?

【问题讨论】:

  • 界面的优雅之处在于它们的用途。确保一个类提供给定的 API(或一组方法)。如果您不想将接口添加到旧代码中,可以手动检查类基(is_a())和接口。(class_implements())php.net/manual/en/function.is-a.phpphp.net/manual/en/function.class-implements.php,确定它不漂亮,你可以在旧类中实现接口而不更改函数名称,然后在新类上使用相同的接口。
  • 嗨@ArtisiticPhoenix:这通常是一个公平的观点,但我无法使用“旧”层次结构(说实话,它实际上是一些系统类)。尽可能尝试依赖类型提示不是更可取吗?如果不需要,提示可以避免手动类型检查的“复杂性”(如果新的提示接口与“旧”类的约定相匹配,则不应该这样)。
  • 是的,这将是首选,但我的意思是您的界面检查将无法允许旧代码进入。但我想您已经知道 =)
  • 这是一个关于 php mixns 的好帖子,我认为您可以使用这种技术来实现您的目标。也来看看关于runkit的回答:stackoverflow.com/questions/6876925/…

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


【解决方案1】:

从你想实现对现有代码所消耗的不同类的统一替换的前提出发,不修改现有消费者,那么我有一个……“解决方案”。

这是当前问题的一个示例:

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:不要理会这些乱七八糟的东西,只需修改消费合约并使用联合接口,就像你打算的那样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-26
    • 2011-08-10
    • 2011-01-21
    • 1970-01-01
    • 2011-03-25
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多