我编写了一些代码,其中包含一组实现简单接口的对象。这些对象只是普通的DTO。他们需要渲染。每个人都需要它自己的渲染器。我最初认为好,所以会有一个渲染器接口,它有一个方法render
,它会接受一个结果ResultInterface
。每个结果项都有不同的额外数据需要呈现。
所以实际发生的是每个渲染器然后检查它是否接收到正确的类型。因此,尽管它似乎接受了实施ResultInterface
的任何内容,但它实际上并没有。然后我想,为什么我甚至打扰ResultInterface
上的类型提示。
以下是一些示例:
<?php
Interface RendererInterface
{
public function render(ResultInterface $result);
}
class ExceptionFailureRenderer implements RendererInterface
{
public function render(ResultInterface $result)
{
if (!$result instanceof ExceptionFailure) {
throw new InvalidArgumentException;
}
}
}
class SomeOtherFailureRenderer implements RendererInterface
{
public function render(ResultInterface $result)
{
if (!$result instanceof SomeOtherFailure) {
throw new InvalidArgumentException;
}
}
}
Interface ResultInterface
{
public function getName();
}
class ExceptionFailure implements ResultInterface
{
public function getName()
{
return 'Exception Failure';
}
public function getException()
{
return $this->exception;
}
}
class SomeOtherFailure implements ResultInterface
{
public function getName()
{
return 'Some Other Failure';
}
public function getSomeOtherPieceOfData()
{
return $this->importantData;
}
}
$renderers = [
ExceptionFailure::class => new ExceptionFailureRenderer,
SomeOtherFailure::class => new SomeOtherFailureRenderer
];
$output = '';
foreach ($results as $result) {
$renderer = $renderers[get_class($result)];
$output .= $renderers->render($result);
}
问题
当Renderer
只期望ResultInterface
时,如何更好地避免调用具体方法?
答案 0 :(得分:1)
您正在使用具有已定义函数的接口,这些接口实现它们的类实际上并未使用。
class ExceptionFailureRenderer (missing implements Renderer)
{
public function render(ResultInterface $result)
{
if (!$result instanceof ExceptionFailure) {
throw new InvalidArgumentException;
}
}
}
如果我要编写这段代码,我会使用一个抽象类,用I前缀加入I到IRenderer和IResultInterface(避免类之间的混淆);
abstract class Renderer implements IRenderer
{
public function render(IResultInterface $result)
{
//abstract logic here
}
}
class ExceptionFailureRenderer extends Renderer
{
//overide the logic if required
}
此外,我将使用RendererFactory,存储库或类似的设计模式来管理要使用的渲染。这样可以防止某些与某些Renderer对象的耦合。
class RenderFactory {
protected $instances = [];
public function registerRenderer(IRenderer $renderer) {
$this->instances[$renderer::class] = $renderer;
}
public function doRender(ResultInterface $resultInterface) {
//logic to retrive the renderer
}
}
$renderFactory = new RenderFactory();
$renderFactory->registerRenderer(new ExceptionFailureRenderer());
$renderFactory->doRender($exceptionFaliureResult);
这当然是优惠的,这不仅仅是对如何做到这一点的回答。我认为你的答案很模糊,因为没有答案。
答案 1 :(得分:1)
你说这里存在设计缺陷是对的。 拥有一个实现接口的对象只会在履行隐含合同时抛出异常,这是一个明显的代码味道。
有很多方法可以重构这个,我就是这样做的。
查看具体的RendererInterface
实现,显然它们与具体的ResultInterface
相关联,因此应该明确声明该依赖关系。 (WTF ??见底部注释)
由于不可变对象通常更容易使用,并且似乎没有合理的理由来重用RendererInterface
实现(实例化它似乎并不昂贵,或者这样做有任何副作用)我决定通过使RendererInterface
方法无参数来改变render
,并将具体的结果对象注入构造函数中:
Interface RendererInterface
{
public function render();
}
class ExceptionFailureRenderer implements RendererInterface
{
private $result;
public function __construct(ExceptionFailure $result)
{
$this->result = $result;
}
public function render()
{
echo '<p>'.$this->result->getException().'</p>';
}
}
class SomeOtherFailureRenderer implements RendererInterface
{
private $result;
public function __construct(SomeOtherFailure $result)
{
$this->result = $result;
}
public function render()
{
echo '<p>'.$this->result->getSomeOtherPieceOfData().'</p>';
}
}
现在混凝土渲染器与它们所需的具体结果相关联,您需要一种方法来获得具体结果的正确渲染器。 显而易见的答案是工厂:
interface RenderFactoryInterface
{
/***
* @param ResultInterface $result
* @return RendererInterface
*/
public function getRenderer(ResultInterface $result);
}
class RenderFactory implements RenderFactoryInterface
{
//could be injected into constructor
private $renderMap = [
ExceptionFailure::class => ExceptionFailureRenderer::class,
SomeOtherFailure::class => SomeOtherFailureRenderer::class
];
public function getRenderer(ResultInterface $result)
{
if(!isset($this->renderMap[get_class($result)])){
//write a more suitable exception class
throw new InvalidArgumentException();
//or perhaps return a defaultRenderer that only works on
//the methods defined in `ResultInterface`
}
return new $this->renderMap[get_class($result)]($result);
}
}
您的消费代码只需要ResultInterface
个对象的集合和RenderFactoryInterface
的实现来完成其工作,不知道集合中特定类型的ResultInterface
,也不知道任何关于如何将ResultInterface
与RendererInterface
:
class Consumer
{
private $results;
private $renderFactory;
/***
* @param ResultInterface[] $results php doesnt have typed collections let
* @param RenderFactoryInterface $renderFactory
*/
public function __construct(array $results, RenderFactoryInterface $renderFactory)
{
$this->results = $results;
$this->renderFactory = $renderFactory;
}
public function doSomething()
{
foreach($this->results as $result){
$this->renderFactory->getRenderer($result)->render();
}
}
}
注意上面是使用php 5,它没有声明返回类型的方法,php 7会让你更具体地说getRenderer
方法应该返回RendererInterface
的实现。
此外,消费者代码只能输入提示数组,希望类型化的集合很快就会到达。
此外,作为"Typehint a concrete class?? WTF are you thinking"
的先发制人,本例旨在尽可能简短。你当然可以创建一个ExceptionFailureInterface
和一个SomeOtherFailureInterface
,输入那些,并让具体的ResultInterface
类实现正确的(和ResultInterface
)如果你认为增加灵活性值得增加复杂性。
我对这个答案的观点是,具体的RendererInterface
类需要一个比ResultInterface
更具体的契约,可以是一个具体的类(如图所示)或更具体的接口