gpt4 book ai didi

php - 六边形架构/干净代码 : Problems implementing adaptor pattern

转载 作者:搜寻专家 更新时间:2023-10-31 21:08:46 25 4
gpt4 key购买 nike

我目前正在 Symfony 2 框架上编写一个小型控制台应用程序。我正在尝试将应用程序与框架隔离开来(主要是在听过一些关于六边形架构/端口和适配器、清洁代码以及将应用程序与框架解耦的有趣演讲后作为练习),以便它可以作为控制台应用程序运行,一个 Web 应用程序,或者毫不费力地迁移到另一个框架。

我遇到的问题是当我的一个接口(interface)是使用适配器模式实现的,它依赖于另一个也使用适配器模式实现的接口(interface)。这很难描述,最好用代码示例来描述。在这里,我在我的类/接口(interface)名称前加上“我的”前缀,只是为了清楚哪些代码是我自己的(我可以编辑)哪些属于 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

为了符合MyDialogInterfaceMySymfonyDialogAdaptor::askConfirmation 方法必须将MyOutputInterface 的实例作为参数。但是,对 Symfony 的 DialogHelper::askConfirmation 方法的调用需要 \Symfony\Component\Console\Output\OutputInterface 的实例,这意味着代码不会运行。

我可以看到几种解决方法,但都不是特别令人满意:

  1. MySymfonyOutputAdaptor 实现 MyOutputInterfaceSymfony\Component\Console\Output\OutputInterface。这并不理想,因为我需要指定该接口(interface)中的所有方法,而我的应用程序只真正关心 writeln 方法。

  2. 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 的一个实例,它应该指定它作为它的类型提示,但这意味着它不再实现 我的对话框界面。此外,在其自己的适配器之外访问底层 ConsoleOutput 对象似乎并不理想,因为它实际上应该由适配器包装。

谁能提出解决这个问题的方法?我觉得我错过了一些东西:也许我把适配器放在了错误的地方,而不是多个适配器,我只需要一个适配器来包装整个输出/对话系统?或者我可能需要包含另一个继承层才能实现这两个接口(interface)?

感谢任何建议。

编辑:此问题与以下拉取请求中描述的问题非常相似:https://github.com/SimpleBus/CommandBus/pull/2

最佳答案

经过与同事的大量讨论(感谢 Ian 和 Owen),加上 Matthias 通过 https://github.com/SimpleBus/CommandBus/pull/2 提供的一些帮助,我们提出了以下解决方案:

<?php

// My code.

interface MyOutputInterface
{
public function writeln($message);
}

class SymfonyOutputToMyOutputAdaptor implements MyOutputInterface
{
private $output;

public function __construct(\Symfony\Component\Console\Output\OutputInterface $output)
{
$this->output = $output;
}

public function writeln($message)
{
$this->output->writeln($message)
}
}

class MyOutputToSymfonyOutputAdapter implements Symfony\Component\Console\Output\OutputInterface
{
private $myOutput;

public function __construct(MyOutputInterface $myOutput)
{
$this->myOutput = $myOutput;
}

public function writeln($message)
{
$this->myOutput->writeln($message);
}

// Implement all methods defined in Symfony's OutputInterface.
}

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)
{
$symfonyOutput = new MyOutputToSymfonyOutputAdapter($output);

$this->dialog->askConfirmation($symfonyOutput, $message);
}
}

// Symfony code.

namespace Symfony\Component\Console\Helper;

class DialogHelper
{
public function askConfirmation(\Symfony\Component\Console\Output\OutputInterface $output, $question, $default = true)
{
// ...
}
}

我认为我遗漏的概念是适配器本质上是单向的(例如,从我的代码到 Symfony 的代码,反之亦然)并且我需要另一个单独的适配器来从 MyOutputInterface 转换回Symfony 的 OutputInterface 类。

这并不完全理想,因为我仍然需要在这个新适配器 (MyOutputToSymfonyOutputAdapter) 中实现所有 Symfony 的方法,但是这个架构确实感觉结构很好,因为很明显每个适配器都在一个方向上转换:我相应地重命名了适配器以使其更清楚。

另一种选择是只完全实现我想要支持的方法(在这个例子中只是writeln)并定义其他方法来抛出异常以指示适配器不支持它们如果他们被称为。

非常感谢大家的帮助。

关于php - 六边形架构/干净代码 : Problems implementing adaptor pattern,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26699118/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com