干净的体系结构:用例包含演示者或返回数据?


42

清洁体系结构建议让交互器调用实际执行的演示者(其被注入时,DIP以下)的处理响应/显示的用例。但是,我看到人们实现了这种体系结构,从交互器返回输出数据,然后让控制器(在适配器层中)决定如何处理它。除了没有明确定义交互器的输入和输出端口之外,第二种解决方案是否将应用程序职责泄漏到应用程序层之外?

输入和输出端口

考虑到Clean Architecture的定义,尤其是描述控制器,用例交互器和演示者之间关系的小流程图,我不确定我是否正确理解“用例输出端口”应该是什么。

像六边形体系结构一样,干净的体系结构区分主要端口(方法)和次要端口(由适配器实现的接口)。按照通信流程,我希望“用例输入端口”是主要端口(因此只是一个方法),而“用例输出端口”是要实现的接口,也许是使用实际适配器的构造函数参数,以便交互器可以使用它。

代码示例

举例来说,这可能是控制器代码:

Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();

演示者界面:

// Use Case Output Port
interface Presenter
{
    public void present(Data data);
}

最后,交互器本身:

class UseCase
{
    private Repository repository;
    private Presenter presenter;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.repository = repository;
        this.presenter = presenter;
    }

    // Use Case Input Port
    public void doSomething()
    {
        Data data = this.repository.getData();
        this.presenter.present(data);
    }
}

在交互者上呼叫演示者

先前的解释似乎由上述图表本身证实,其中控制器和输入端口之间的关系由带有“尖”头(UML表示“关联”,意思是“具有”)的实心箭头表示。控制器“有一个”用例),而演示者与输出端口之间的关系由带有“白色”头的实心箭头表示(UML代表“继承”,不是“实现”那个),但可能无论如何,这就是意义。

此外,在对另一个问题的回答中,Robert Martin确切描述了一个用例,其中交互器根据读取请求调用演示者:

单击地图会导致placePinController被调用。它收集点击的位置以及任何其他上下文数据,构造一个placePinRequest数据结构,并将其传递给PlacePinInteractor,后者检查引脚的位置,并在必要时对其进行验证,创建一个Place实体来记录该引脚,并构造一个EditPlaceReponse对象,并将其传递给EditPlacePresenter,它会打开位置编辑器屏幕。

为了使它在MVC中很好地发挥作用,我可以认为传统上将进入控制器的应用程序逻辑已移至交互器,因为我们不希望任何应用程序逻辑泄漏到应用程序层之外。适配器层中的控制器将仅调用交互器,并可能在此过程中进行一些次要的数据格式转换:

该层中的软件是一组适配器,可以将数据从对用例和实体最方便的格式转换为对某些外部机构(如数据库或Web)最方便的格式。

从原始文章开始,讨论接口适配器。

在交互器上返回数据

但是,这种方法的问题在于用例必须照顾到演示文稿本身。现在,我看到Presenter接口的目的是要足够抽象,以表示几种不同类型的演示者(GUI,Web,CLI等),并且它实际上仅表示“输出”,这可能是一个用例很好,但我对此并不完全有信心。

现在,在网上寻找干净架构的应用程序,我似乎只发现人们将输出端口解释为返回某些DTO的方法。就像这样:

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);

// I'm omitting the changes to the classes, which are fairly obvious

之所以具有吸引力,是因为我们将“调用”演示文稿的责任从用例中移出了,因此用例不再与知道如何处理数据有关,而与提供数据无关。而且,在这种情况下,我们仍然没有违反依赖关系规则,因为用例仍然对外层一无所知。

但是,用例不再控制执行实际演示的时间(这可能很有用,例如,在那时候做一些额外的工作,例如记录日志,或者在必要时完全中止操作)。另外,请注意我们丢失了用例输入端口,因为现在控制器仅使用该getData()方法(这是我们的新输出端口)。此外,在我看来,我们在这里违反了“告诉,不要问”的原则,因为我们是在向交互者请求一些数据来执行某些操作,而不是告诉它在实际操作中执行实际操作。第一名。

要点

那么,根据清洁架构,这两种选择中的任何一种是否都是用例输出端口的“正确”解释?他们俩都可行吗?


3
强烈建议不要交叉发布。如果您想在这里居住问题,则应从Stack Overflow中删除它。
罗伯特·哈维

Answers:


48

干净架构建议让用例交互器调用演示者的实际实现(在DIP之后注入)以处理响应/显示。但是,我看到人们实现了这种体系结构,从交互器返回输出数据,然后让控制器(在适配器层中)决定如何处理它。

那肯定不是CleanOnionHexagonal Architecture。就是这样

在此处输入图片说明

并不是说MVC必须这样做

在此处输入图片说明

您可以使用许多不同的方法在模块之间进行通信并将其称为MVC。告诉我使用MVC的东西并不能真正告诉我组件如何通信。那不是标准化的。它告诉我的是,至少有三个组成部分专注于它们的三个职责。

其中一些方式已被赋予不同的名称在此处输入图片说明

这些中的每一个都可以合理地称为MVC。

无论如何,这些都没有真正捕捉到流行语体系结构(Clean,Onion和Hex)都在要求您做什么。

在此处输入图片说明

添加被抛弃的数据结构(由于某种原因将其上下翻转),您将得到

在此处输入图片说明

这里应该清楚的一件事是,响应模型不会通过控制器行进。

如果您精打细算,您可能已经注意到,只有流行语架构才能完全避免循环依赖。重要的是,这意味着代码更改的影响不会通过在组件之间循环而传播。当更改达到无关紧要的代码时,更改将停止。

不知道他们是否将其颠倒过来,以便控制流顺时针通过。接下来,还有这些“白色”箭头。

除了没有明确定义交互器的输入和输出端口之外,第二种解决方案是否将应用程序职责泄漏到应用程序层之外?

由于从Controller到Presenter的通信是要通过应用程序“层”进行的,因此,是的,使Controller成为Presenters的一部分工作很可能会导致泄漏。这是我对VIPER建筑的主要批评。

为什么分离这些是如此重要,可能可以通过研究命令查询责任隔离来最好地理解。

输入和输出端口

考虑到Clean Architecture的定义,尤其是描述控制器,用例交互器和演示者之间关系的小流程图,我不确定我是否正确理解“用例输出端口”应该是什么。

对于此特定用例,就是通过其发送输出的API。仅此而已。此用例的交互器不需要知道也不需要知道输出是否将输出到GUI,CLI,日志或音频扬声器。交互器需要知道的就是最简单的API,它将使它报告其工作结果。

像六边形体系结构一样,干净的体系结构区分主要端口(方法)和次要端口(由适配器实现的接口)。按照通信流程,我希望“用例输入端口”是主要端口(因此只是一个方法),而“用例输出端口”是要实现的接口,也许是使用实际适配器的构造函数参数,以便交互器可以使用它。

输出端口与输入端口不同的原因是,它抽象的层一定不能拥有它。也就是说,不允许抽象层来指示对其进行更改。只有应用程序层及其作者才能确定输出端口可以更改。

这与抽象层拥有的输入端口相反。只有应用程序层作者才能决定是否应更改其输入端口。

遵循这些规则保留了以下想法:应用程序层或任何内部层对外部层一无所知。


在交互者上呼叫演示者

先前的解释似乎由上述图表本身证实,其中控制器和输入端口之间的关系由带有“尖”头(UML表示“关联”,意思是“具有”)的实心箭头表示。控制器“有一个”用例),而演示者与输出端口之间的关系由带有“白色”头的实心箭头表示(UML代表“继承”,不是“实现”那个),但可能无论如何,这就是意义。

关于“白色”箭头的重要一点是,它可以让您执行以下操作:

在此处输入图片说明

您可以让控制流朝相反的依赖方向前进!这意味着内层不必了解外层,但是您可以深入内层再回来!

这样做与使用“ interface”关键字无关。您可以使用抽象类来实现。哎呀,只要可以扩展,就可以用(ick)具体的类来做。仅仅专注于定义Presenter必须实现的API就是一件很好的事。空心箭头仅要求多态性。哪种取决于您。

为什么逆转这种依赖性的方向如此重要,可以通过研究依赖性反转原理来了解。我在这里将该原理映射到这些图中。

在交互器上返回数据

但是,这种方法的问题是用例必须照顾到演示文稿本身。现在,我看到Presenter界面的目的是要足够抽象,以表示几种不同类型的Presenter(GUI,Web,CLI等),并且它实际上仅表示“输出”,这是一个用例也许会有,但我仍然对此并不完全有信心。

不,是真的。确保内层不了解外层的关键是我们可以删除,替换或重构外层,确信这样做不会破坏内层的任何内容。他们不知道的事不会伤害他们。如果我们能够做到,那么我们可以将外部的改变为我们想要的任何东西。

现在,在网上寻找干净架构的应用程序,我似乎只发现人们将输出端口解释为返回某些DTO的方法。就像这样:

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious

之所以具有吸引力,是因为我们将“调用”演示文稿的责任从用例中移出了,因此用例不再与知道如何处理数据有关,而与提供数据无关。而且,在这种情况下,我们仍然没有违反依赖关系规则,因为用例仍然对外层一无所知。

现在的问题是,知道如何请求数据的人也必须是接受数据的人。在Controller能够极好地调用Usecase Interactor之前,他们没有意识到响应模型的外观,应将其放在何处以及如何呈现它。

同样,请研究“ 命令查询责任隔离”以了解其重要性。

但是,用例不再控制执行实际演示的时间(这可能很有用,例如,在那时候做一些额外的工作,例如记录日志,或者在必要时完全中止操作)。另外,请注意我们丢失了用例输入端口,因为现在控制器仅使用getData()方法(这是我们的新输出端口)。此外,在我看来,我们在这里违反了“告诉,不要问”的原则,因为我们是在向交互者请求一些数据来执行某些操作,而不是告诉它在实际操作中执行实际操作。第一名。

是! 说而不是问,将有助于保持面向对象而不是过程。

要点

那么,根据清洁架构,这两种选择中的任何一种是否都是用例输出端口的“正确”解释?他们俩都可行吗?

任何可行的方法都是可行的。但是我不会说您忠实提出的第二个选项是“清洁架构”。这可能是可行的。但这不是Clean Architecture所要求的。


4
感谢您抽出宝贵的时间来撰写如此深入的说明。
swahnee

1
我一直在努力把自己的头围在“干净架构”上,而这个答案是一个了不起的资源。干的很好!
弥敦道

伟大而详细的答案..谢谢。.您能给我一些有关UseCase运行期间更新GUI的提示(或指向解释),即在上传大文件时更新进度条吗?
Ewoks

1
@Ewoks,作为对您的问题的快速解答,您应该研究Observable模式。您的用例可以返回主题,并通知主题进度更新。演示者将订阅主题并响应通知。
弥敦道

7

在与您的问题有关的讨论中,鲍伯叔叔在其“干净的体系结构”中解释了演示者的目的:

给定此代码示例:

namespace Some\Controller;

class UserController extends Controller {
    public function registerAction() {
        // Build the Request object
        $request = new RegisterRequest();
        $request->name = $this->getRequest()->get('username');
        $request->pass = $this->getRequest()->get('password');

        // Build the Interactor
        $usecase = new RegisterUser();

        // Execute the Interactors method and retrieve the response
        $response = $usecase->register($request);

        // Pass the result to the view
        $this->render(
            '/user/registration/template.html.twig', 
            array('id' =>  $response->getId()
        );
    }
}

鲍勃叔叔说:

演示者的目的是将用例与UI格式分离。 在您的示例中,$ response变量由交互者创建,但由视图使用。这将交互者与视图耦合。例如,假设$ response对象中的一个字段是日期,则该字段将是一个二进制日期对象,可以用许多不同的日期格式呈现。需要一个非常特定的日期格式,例如DD / MM / YYYY。谁负责创建格式?如果交互器创建的格式,那么它知道太多关于观。但是,如果认为需要二进制日期对象,然后它知道太多关于交互件。

“主持人的工作就是拿响应对象中的数据并将其格式化为视图。 视图和交互器都不了解彼此的格式。

-鲍勃叔叔

(更新:2019年5月31日)

鉴于鲍勃叔叔的回答,我认为我们是否执行选项1(让交互者使用演示者)都没关系 ...

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

...或者我们执行选项2(让交互者返回响应,在控制器内部创建一个演示者,然后将响应传递给演示者)...

class Controller
{
    public void ExecuteUseCase(Data data)
    {
        Request request = ...
        UseCase useCase = new UseCase(repository);
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        presenter.Show(response);
    }
}

就个人而言,我更喜欢选择#1,因为我希望能够控制interactor 何时显示数据和错误消息,如下例所示:

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

...我希望能够做到if/elseinteractor交互器内部而不是外部的呈现相关的操作。

如果在另一方面,我们做选择#2,我们将不得不存储错误消息(S)的response对象,返回response从对象interactorcontroller,使controller 解析response对象...

class UseCase
{
    public Response Execute(Request request)
    {
        Response response = new Response();
        if (<invalid request>) 
        {
            response.AddError("...");
        }

        if (<there is another error>) 
        {
            response.AddError("another error...");
        }

        if (response.HasNoErrors)
        {
            response.Whatever = ...
        }

        ...
        return response;
    }
}
class Controller
{
    private UseCase useCase;

    public Controller(UseCase useCase)
    {
        this.useCase = useCase;
    }

    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        if (response.ErrorMessages.Count > 0)
        {
            if (response.ErrorMessages.Contains(<invalid request>))
            {
                presenter.ShowError("...");
            }
            else if (response.ErrorMessages.Contains("another error")
            {
                presenter.ShowError("another error...");
            }
        }
        else
        {
            presenter.Show(response);
        }
    }
}

我不喜欢解析response数据中的错误,controller因为如果这样做,我们会做多余的工作---如果我们更改了中的某些内容,那么interactor我们还必须更改中的某些内容controller

此外,如果我们后来决定将我们的重用interactor使用控制台,例如目前的数据,我们要记住复制粘贴那些if/elsecontroller我们的控制台应用程序的。

// in the controller for our console app
if (response.ErrorMessages.Count > 0)
{
    if (response.ErrorMessages.Contains(<invalid request>))
    {
        presenterForConsole.ShowError("...");
    }
    else if (response.ErrorMessages.Contains("another error")
    {
        presenterForConsole.ShowError("another error...");
    }
}
else
{
    presenterForConsole.Present(response);
}

如果我们使用选项#1,则if/else 只能在一个地方使用它interactor


如果您使用的是ASP.NET MVC(或其他类似的MVC框架),则选择#2是更简单的方法。

但是我们仍然可以在这种环境中执行选项1。这是在ASP.NET MVC中执行选项#1的示例:

(注意,我们需要public IActionResult Result在ASP.NET MVC应用的演示者中拥有)

class UseCase
{
    private Repository repository;

    public UseCase(Repository repository)
    {
        this.repository = repository;
    }

    public void Execute(Request request, Presenter presenter)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {
            ...
        }
        this.presenter.Show(response);
    }
}
// controller for ASP.NET app

class AspNetController
{
    private UseCase useCase;

    public AspNetController(UseCase useCase)
    {
        this.useCase = useCase;
    }

    [HttpPost("dosomething")]
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new AspNetPresenter();
        useCase.Execute(request, presenter);
        return presenter.Result;
    }
}
// presenter for ASP.NET app

public class AspNetPresenter
{
    public IActionResult Result { get; private set; }

    public AspNetPresenter(...)
    {
    }

    public async void Show(Response response)
    {
        Result = new OkObjectResult(new { });
    }

    public void ShowError(string errorMessage)
    {
        Result = new BadRequestObjectResult(errorMessage);
    }
}

(注意,我们需要public IActionResult Result在ASP.NET MVC应用的演示者中拥有)

如果我们决定为控制台创建另一个应用程序,则可以重复使用UseCase上述内容,并仅为控制台创建ControllerPresenter

// controller for console app

class ConsoleController
{    
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new ConsolePresenter();
        useCase.Execute(request, presenter);
    }
}
// presenter for console app

public class ConsolePresenter
{
    public ConsolePresenter(...)
    {
    }

    public async void Show(Response response)
    {
        // write response to console
    }

    public void ShowError(string errorMessage)
    {
        Console.WriteLine("Error: " + errorMessage);
    }
}

(注意,我们没有public IActionResult Result控制台应用程序的演示者)


感谢您的贡献。但是,阅读对话时,我不了解一件事:他说,演示者应该呈现来自响应的数据,同时,交互者不应该创建响应。但是,谁在创建响应?我要说的是,交互者应该以演示者知道的特定于应用程序的格式将数据提供给演示者,因为适配器层可以依赖于应用程序层(但不能反过来)。
swahnee

对不起。也许会引起混淆,因为我没有包含讨论中的代码示例。我将对其进行更新以包括代码示例。
Jboy Flaga's

鲍勃叔叔没有说互动者不应该做出回应。响应将由交互器创建。鲍伯叔叔的意思是,演示者将使用交互者创建的响应。然后,演示者将对其“格式化”,将格式化后的响应放入视图模型,然后将该视图模型传递给视图。<br/>我就是这样理解的。
Jboy Flaga's

1
这更有意义。我的印象是“视图”是“演示者”的代名词,因为Clean Architecture既没有提及“视图”也没有提及“视图模型”,我认为它们仅仅是MVC概念,在实现适配器。
swahnee

2

用例可以包含演示者或返回数据,这取决于应用程序流程的要求。

在了解不同的应用程序流程之前,让我们先了解几个术语:

  • 域对象:域对象是域层中承载业务逻辑操作的数据容器。
  • 视图模型:域对象通常映射到应用程序层中的视图模型,以使它们兼容且对用户界面友好。
  • 演示者:虽然应用程序层中的控制器通常会调用用例,但建议将域委派以将模型映射逻辑委托给单独的类(遵循“单一职责原则”),称为“演示者”。

包含返回数据的用例

在通常情况下,用例只是将域对象返回给应用程序层,可以在应用程序层中对其进行进一步处理以使其友好地显示在UI中。

由于控制器负责调用用例,因此在这种情况下,它还包含各个演示者的引用,以在将模型发送给要呈现的视图之前进行域到模型的映射。

这是一个简化的代码示例:

namespace SimpleCleanArchitecture
{
    public class OutputDTO
    {
        //fields
    }

    public class Presenter 
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    public class Domain
    {
        //fields
    }

    public class UseCaseInteractor
    {
        public Domain Process(Domain domain)
        {
            // additional processing takes place here
            return domain;
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            UseCaseInteractor userCase = new UseCaseInteractor();
            var domain = userCase.Process(new Domain());//passing dummy domain(for demonstration purpose) to process
            var presenter = new Presenter();//presenter might be initiated via dependency injection.

            return new View(presenter.Present(domain));
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

包含演示者的用例

尽管不常见,但用例可能需要致电演示者。在那种情况下,建议不要将接口(或抽象类)作为参考点(应在运行时通过依赖项注入对其进行初始化),而不是保存演示者的具体参考。

在一个单独的类中(而不是在控制器内部)具有用于查看模型映射逻辑的域,这也会破坏控制器和用例之间的循环依赖关系(当用例类需要引用映射逻辑时)。

在此处输入图片说明

下面是原始文章中所示的控制流的简化实现,演示了如何实现。请注意,与图中所示不同,为简单起见,UseCaseInteractor是一个具体的类。

namespace CleanArchitectureWithPresenterInUseCase
{
    public class Domain
    {
        //fields
    }

    public class OutputDTO
    {
        //fields
    }

    // Use Case Output Port
    public interface IPresenter
    {
        OutputDTO Present(Domain domain);
    }

    public class Presenter: IPresenter
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    // Use Case Input Port / Interactor   
    public class UseCaseInteractor
    {
        IPresenter _presenter;
        public UseCaseInteractor (IPresenter presenter)
        {
            _presenter = presenter;
        }

        public OutputDTO Process(Domain domain)
        {
            return _presenter.Present(domain);
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            IPresenter presenter = new Presenter();//presenter might be initiated via dependency injection.
            UseCaseInteractor userCase = new UseCaseInteractor(presenter);
            var outputDTO = userCase.Process(new Domain());//passing dummy domain (for demonstration purpose) to process
            return new View(outputDTO);
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}


1

用例包含演示者或返回数据?

那么,根据清洁架构,这两种选择中的任何一种是否都是用例输出端口的“正确”解释?他们俩都可行吗?


简而言之

是的,只要这两种方法都考虑到了业务层和交付机制之间的控制反转,它们都是可行的。通过第二种方法,我们仍然能够通过使用观察者,中介者以及其他很少的设计模式来引入IOC。

Bob叔叔使用他的Clean Architecture,试图合成一堆已知的体系结构,以揭示重要的概念和组件,以使我们广泛遵守OOP原则。

将他的UML类图(下图)视为唯一的“ 干净架构”设计会适得其反。可以为具体示例绘制此图……但是,由于它不如通常的体系结构表示法那么抽象,因此他不得不做出具体选择,其中交互器输出端口设计只是实现细节 ……

Bob叔叔的Clean Architecture UML类图


我的两分钱

我更喜欢返回的主要原因UseCaseResponse是,这种方法使我的用例保持灵活,同时允许它们之间的组合通用性泛化特定生成)。一个基本的例子:

// A generic "entity type agnostic" use case encapsulating the interaction logic itself.
class UpdateUseCase implements UpdateUseCaseInterface
{
    function __construct(EntityGatewayInterface $entityGateway, GetUseCaseInterface $getUseCase)
    {
        $this->entityGateway = $entityGateway;
        $this->getUseCase = $getUseCase;
    }

    public function execute(UpdateUseCaseRequestInterface $request) : UpdateUseCaseResponseInterface
    {
        $getUseCaseResponse = $this->getUseCase->execute($request);

        // Update the entity and build the response...

        return $response;
    }
}

// "entity type aware" use cases encapsulating the interaction logic WITH the specific entity type.
final class UpdatePostUseCase extends UpdateUseCase;
final class UpdateProductUseCase extends UpdateUseCase;

注意,类似地,UML用例彼此更接近,包括/彼此扩展,并且被定义为在不同主题(实体)上重用


在交互器上返回数据

但是,用例不再控制执行实际演示的时间(这可能很有用,例如,在那时候做一些额外的工作,例如记录日志,或者在必要时完全中止操作)。

不确定是否要理解这意味着什么,为什么需要“控制”演示文稿的执行?只要不返回用例响应,就不控制它吗?

用例可以在其响应中返回状态代码,以告知客户端层在其操作过程中到底发生了什么。HTTP响应状态代码特别适合描述用例的运行状态…

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.