使用类型安全方法参数的子类


Use subclass of a typesafe method parameter

我尝试在带有typesafe参数的方法中使用子类。不知道我是否遗漏了什么,或者解决方案是不使参数类型安全。在这种情况下,最干净的设计选项是什么?

查看以下代码:

<?php
class MyObject {
    public $name;   
}
class MySubObject extends MyObject {
    public $subProperty;    
}
abstract class Renderer {
    protected $someProperty;
    public abstract function render(MyObject $obj);
}
class RendererA extends Renderer {
    public function render(MyObject $obj) {
        return $this->someProperty . ': ' . $obj->name;
    }
}
// This is the problematic case ------|
class RendererB extends Renderer { // |
    public function render(MySubObject $obj) {
        return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
    }
}
/* EOF */

看看这个:https://github.com/symfony/Routing/blob/2.4/Router.php#L225

在这段代码中,路由器将匹配一个请求,但它有两种方法来实现这一点,这取决于它拥有的Matcher实例的类型。

该案例使用接口来区分对象类型,但是要对您的案例执行相同的技术,请尝试以下操作:

class MyObject {
    public $name;
}
class MySubObject extends MyObject {
    public $subProperty;
}
class Renderer {
    public function render(MyObject $obj) {
        if($obj instanceof MySubObject) {
            return $this->someProperty . ': ' . $obj->name . ' ' .$obj->subProperty;
        }
        return $this->someProperty . ': ' . $obj->name;
    }
}

假设这是功能的唯一变体,则不需要AbstractRenderer。由于MySubObject扩展了MyObject,因此使用扩展类作为参数仍然是类型安全的。

编辑:

此示例使用访问者模式的基本形式。Renderer本质上成为多个潜在处理程序的父管理器。

根据输入的属性(在这种情况下是类,但有更高级的标准形式),在配置阶段添加的驱动程序会对输入进行不同的处理。

此示例可以在命名空间之外执行。

interface TemplateInterface
{
    /**
     * @return string
     */
    public function getName();
}
interface DriverInterface
{
    /**
     * @param TemplateInterface $template
     * @return string
     */
    public function doRender(TemplateInterface $template);
}
class Renderer
{
    /**
     * @var array
     */
    protected $drivers;
    protected $property = 'Sample Property';
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->drivers = array();
    }
    /**
     * @param TemplateInterface $template
     * @return string
     */
    public function render(TemplateInterface $template)
    {
        $class = get_class($template);
        if(false === isset($this->drivers[$class])) {
            throw new InvalidArgumentException(sprintf('Renderer Driver supporting class "%s" is not present.', $class));
        }
        return sprintf('%s: %s', $this->property, $this->drivers[$class]->doRender($template));
    }
    /**
     * @param DriverInterface $driver
     * @param $class
     * @return $this
     */
    public function addDriver(DriverInterface $driver, $class)
    {
        $this->drivers[$class] = $driver;
        return $this;
    }
}
class MyTemplate implements TemplateInterface
{
    public function getName()
    {
        return 'my_template';
    }
}
class MyOtherTemplate implements TemplateInterface
{
    public function getName()
    {
        return 'my_other_template';
    }
    public function getOtherProperty()
    {
        return 'this is another property';
    }
}
class MyDriver implements DriverInterface
{
    public function doRender(TemplateInterface $template)
    {
        return $template->getName();
    }
}
class MyOtherDriver implements DriverInterface
{
    public function doRender(TemplateInterface $template)
    {
        if(false === $template instanceof MyOtherTemplate) {
            throw new InvalidaArgumentException('OtherDriver::doRender argument must be an instance of MyOtherTemplate');
        }
        return sprintf('%s %s', $template->getName(), $template->getOtherProperty());
    }
}
$renderer = new Renderer();
$renderer
    ->addDriver(new MyDriver(),      'MyTemplate')
    ->addDriver(new MyOtherDriver(), 'MyOtherTemplate')
;
echo '<pre>';
echo $renderer->render(new MyTemplate()).PHP_EOL;
echo $renderer->render(new MyOtherTemplate());

这是一个更高级的例子。这一次你没有指定模板的类,而是只添加了驱动程序,但这个驱动程序接口需要实现supports方法。渲染器将遍历每个驱动程序,询问它们是否支持该模板。

第一个返回true的驱动程序将渲染并返回模板。

interface DriverInterface
{
    /**
     * @param TemplateInterface $template
     * @return string
     */
    public function doRender(TemplateInterface $template);
    /**
     * @param TemplateInterface $template
     * @return bool
     */
    public function supports(TemplateInterface $template);
}
class Renderer
{
    /**
     * @var array
     */
    protected $drivers;
    protected $property = 'Sample Property';
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->drivers = array();
    }
    /**
     * Returns the rendered template, or false if the template was not supported by any driver.
     *
     * @param TemplateInterface $template
     * @return string|false
     */
    public function render(TemplateInterface $template)
    {
        $class = get_class($template);
        foreach($this->drivers as $driver) {
            if($driver->supports($template)) {
                return sprintf('%s: %s', $this->property, $driver->doRender($template));
            }
        }
        return false;
    }
    /**
     * @param DriverInterface $driver
     * @param $class
     * @return $this
     */
    public function addDriver(DriverInterface $driver)
    {
        $this->drivers[] = $driver;
        return $this;
    }
}