In a discussion related to your question,Bob 大叔在他的 Clean Architecture 中解释了演示者的目的:
鉴于此代码示例:
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。创建格式是谁的责任?如果交互器创建该格式,那么它对视图了解太多。但是如果视图采用二进制日期对象,那么它对视图了解太多交互者。
“演示者的工作是从响应对象中获取数据并为视图设置格式。视图和交互者都不知道彼此的格式。”
--- 鲍勃叔叔
鉴于 Bob 大叔的回答,我认为 我们是否执行 选项 #1(让交互者使用演示者)没有那么重要... p>
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);
}
}
...我希望能够在interactor 内部而不是在交互器外部执行这些与演示相关的if/else。
另一方面,如果我们执行选项 #2,我们必须将错误消息存储在 response 对象中,将 response 对象从 interactor 返回到 controller,并且使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);
}
}
}
我不喜欢在 controller 中解析 response 数据中的错误,因为如果我们这样做,我们正在做多余的工作 --- 如果我们更改 interactor 中的某些内容,我们还必须更改其中的某些内容controller。
此外,如果我们稍后决定重用我们的 interactor 来使用控制台呈现数据,例如,我们必须记住将所有这些 if/else 复制粘贴到我们控制台应用程序的 controller 中。
// 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 的示例:
(请注意,我们的 ASP.NET MVC 应用程序的演示者中需要有 public IActionResult Result)
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);
}
}
(请注意,我们的 ASP.NET MVC 应用程序的演示者中需要有 public IActionResult Result)
如果我们决定为控制台创建另一个应用程序,我们可以重用上面的 UseCase 并为控制台创建 Controller 和 Presenter:
// 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)