我目前正在 Symfony 2 框架上编写一个小型控制台应用程序。我正在尝试将应用程序与框架隔离(主要是在听到一些关于六边形架构/端口和适配器、干净代码和将应用程序与框架解耦的有趣讨论之后作为练习),以便它可以作为控制台应用程序运行,一个 Web 应用程序,或者毫不费力地迁移到另一个框架。
我遇到的问题是我的一个接口是使用适配器模式实现的,它依赖于另一个也使用适配器模式实现的接口。这很难描述,最好用代码示例来描述。在这里,我在我的类/接口名称前加上“My”,只是为了清楚哪些代码是我自己的(我可以编辑),哪些属于 Symfony 框架。
// My code.
interface MyOutputInterface
{
public function writeln($message);
}
class MySymfonyOutputAdaptor implements MyOutputInterface
{
private $output;
public function __construct(\Symfony\Component\Console\Output\ConsoleOutput $output)
{
$this->output = $output;
}
public function writeln($message)
{
$this->output->writeln($message)
}
}
interface MyDialogInterface
{
public function askConfirmation(MyOutputInterface $output, $message);
}
class MySymfonyDialogAdaptor implements MyDialogInterface
{
private $dialog;
public function __construct(\Symfony\Component\Console\Helper\DialogHelper $dialog)
{
$this->dialog = $dialog;
}
public function askConfirmation(MyOutputInterface $output, $message)
{
$this->dialog->askConfirmation($output, $message); // Fails: Expects $output to be instance of \Symfony\Component\Console\Output\OutputInterface
}
}
// Symfony code.
namespace Symfony\Component\Console\Helper;
class DialogHelper
{
public function askConfirmation(\Symfony\Component\Console\Output\OutputInterface $output, $question, $default = true)
{
// ...
}
}
需要注意的另一件事是\Symfony\Component\Console\Output\ConsoleOutput
实现\Symfony\Component\Console\Output\OutputInterface
.
为了符合MyDialogInterface
,该MySymfonyDialogAdaptor::askConfirmation
方法必须将 的实例MyOutputInterface
作为参数。然而,对 SymfonyDialogHelper::askConfirmation
方法的调用需要一个 的实例\Symfony\Component\Console\Output\OutputInterface
,这意味着代码不会运行。
我可以看到几种解决方法,但都不是特别令人满意:
同时
MySymfonyOutputAdaptor
实现MyOutputInterface
和Symfony\Component\Console\Output\OutputInterface
。这并不理想,因为我需要在该接口中指定所有方法,而我的应用程序只真正关心该writeln
方法。假设
MySymfonyDialogAdaptor
传递给它的对象是MySymfonyOutputAdaptor
: 如果不是,则抛出异常。然后在类中添加一个方法来MySymfonyOutputAdaptor
获取底层\Symfony\Component\Console\Output\ConsoleOutput
对象,该对象可以直接传递给 Symfony 的DialogHelper::askConfirmation
方法(因为它实现了 Symfony 的OutputInterface
)。这将类似于以下内容:class MySymfonyOutputAdaptor implements MyOutputInterface { private $output; public function __construct(\Symfony\Component\Console\Output\ConsoleOutput $output) { $this->output = $output; } public function writeln($message) { $this->output->writeln($message) } public function getSymfonyConsoleOutput() { return $this->output; } } class MySymfonyDialogAdaptor implements MyDialogInterface { private $dialog; public function __construct(\Symfony\Component\Console\Helper\DialogHelper $dialog) { $this->dialog = $dialog; } public function askConfirmation(MyOutputInterface $output, $message) { if (!$output instanceof MySymfonyOutputAdaptor) { throw new InvalidArgumentException(); } $symfonyConsoleOutput = $output->getSymfonyConsoleOutput(); $this->dialog->askConfirmation($symfonyConsoleOutput, $message); } }
这感觉不对:如果
MySymfonyDialogAdaptor::askConfirmation
要求它的第一个参数是 MySymfonyOutputAdaptor 的一个实例,它应该将它指定为它的类型提示,但这意味着它不再实现MyDialogInterface
. 此外,在它自己的适配器之外访问底层ConsoleOutput
对象似乎并不理想,因为它实际上应该由适配器包装。
任何人都可以提出解决这个问题的方法吗?我觉得我错过了一些东西:也许我把适配器放在了错误的地方,而不是多个适配器,我只需要一个适配器来包装整个输出/对话系统?或者也许我需要包含另一个继承层才能实现这两个接口?
任何建议表示赞赏。
编辑:此问题与以下拉取请求中描述的问题非常相似:https ://github.com/SimpleBus/CommandBus/pull/2