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