使用typesafe方法参数的子类

时间:2014-04-15 12:29:42

标签: php oop

我尝试在带有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 */

1 个答案:

答案 0 :(得分:1)

看看这个: 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,因此使用扩展类作为参数仍然是类型安全的。

编辑:

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

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

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

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;
    }
}