如何确定应如何获得自己的控制器?


10

我在用PHP构建的Web应用程序中使用MVC模式。

我一直在努力确定是否需要一套新的专用控制器来执行一组操作,或者是否应该将它们放置在已经存在的控制器内。

创建控制器时,有什么好的经验法则可以遵循吗?

例如,我可以拥有:

AuthenticationController 动作:

  • index() 显示登录表单。
  • submit() 处理表单提交。
  • logout(),不言自明。

要么

LoginController 动作:

  • index() 显示登录表单。
  • submit() 处理表单提交。

LogoutController 采取行动:

  • index() 处理注销。

要么

AccountController 动作:

  • loginGet() 显示登录表单。
  • loginPost() 处理登录表单提交。
  • logoutGet() 处理注销。
  • registerGet() 显示注册表。
  • registerPost() 处理表单提交。

    以及帐户涉及的任何其他操作。


也许看看RESTful设计。它不能解决此类问题,但是可以为您提供一个很好的指导。
thorstenmüller2014年

Answers:


3

为了找到适合控制器的分组,请考虑测试

(即使您实际上没有进行任何测试,思考如何测试控制器也将为您提供有关如何构造它们的非常深刻的见解。)

An AuthenticationController本身无法测试,因为它仅包含用于登录和注销的功能,但是您的测试代码将需要以某种方式创建用于测试目的的假帐户,然后才能测试成功的登录。您可以绕过被测子系统,而直接进入模型以创建测试帐户,但是随后您将获得一个脆弱的测试:如果模型发生更改,则不仅需要修改测试的代码,该模型,以及测试控制器的代码,即使控制器的接口和行为保持不变。没道理

LoginController出于同样的原因,A 也不合适:您必须先创建帐户才能测试它,并且还有更多无法测试的事情,例如防止重复登录,但允许用户注销后再登录。(由于该控制器没有注销功能。)

An AccountController会为您提供进行测试所需的一切:您可以创建一个测试帐户,然后尝试登录,可以删除该帐户,然后确保您无法再次登录,可以更改密码并确保必须使用正确的密码才能登录等。

结论:为了编写最小的测试套件,您将需要提供所有AccountController可用的功能。将其细分为较小的控制器似乎会产生功能不足以进行正确测试的残障控制器。这很好地表明的功能AccountController是有意义的最小细分。

一般来说,“测试思考”方法不仅适用于这种特定情况,而且将来会遇到任何类似情况。


1

答案不是那么明显

在做出任何答辩之前,请让我澄清一些事情。首先:

什么是控制器?

控制器是控制请求的系统的一部分-分派后。因此,我们可以将其定义为与...有关的一组动作

控制器的范围是什么?

当我们有任何答案时,这就是或多或少的一部分。你怎么看?它是事物的控制者(例如帐户)还是操作的控制者?当然,它是某种模型的控制器,或者是某种对其提供操作的抽象事物。

答案是...

带有操作的AuthenticationController:

  • index()以显示登录表单。
  • Submit()处理表单提交。
  • logout(),不言自明。

不,认证是一个过程。不要那样

带操作的LoginController:

  • index()以显示登录表单。
  • Submit()处理表单提交。

同样在这里。登录-操作。最好不要创建动作控制器(您没有与其关联的模型)。

具有操作的AccountController:

  • loginGet()以显示登录表单。
  • loginPost()处理登录表单的提交。
  • logoutGet()处理注销。
  • registerGet()显示注册表格。
  • registerPost()处理表单提交。

很好,但是我不相信构建低级控制器(控制器本身就是抽象)值得一提。无论如何,用* Get或* Post创建方法尚不清楚。

有什么建议吗?

是的,请考虑一下:

AccountController:

  • 登录(AccountModel)
  • 注销(AccountModel)
  • 注册(AccountModel)
  • 指数()

以及与此相关的模型,即帐户类。这将使您有机会将模型控制器对移动到其他地方(如果需要)并编写清晰的代码(很明显,该login()方法的含义)。尤其是在CRUD应用程序中,对模型进行贴图确实很有名,也许这是您的一种方式。


1

通常为特定资源(实体类,数据库中的表)创建控制器,但也可以创建控制器以将负责应用程序特定部分的动作组合在一起。在您的示例中,这将是一个处理应用程序安全性的控制器:

class SecurityController
{
    // can handle both the login page display and
    // the login page submission
    login(); 

    logout();

    register();

    // optional: confirm account after registration
    confirm();

    // displays the forgot password page
    forgotPassword();

    // displays the reset password page
    // and handle the form submission
    resetPassword();
}

注意:请勿将与安全性相关的操作和用户配置文件操作放在同一控制器中;这可能是有道理的,因为它们与用户有关,但一个应处理身份验证,另一个应处理电子邮件,名称等更新。

使用为资源创建的控制器(假设Task),您将具有通常的CRUD操作:

class TasksController
{
    // usually displays a paginated list of tasks
    index();

    // displays a certain task, based on an identifier
    show(id);

    // displays page with form and
    // handles form submission for creating
    // new tasks
    create();

    // same as create(), but for changing records
    update(id);     

    // displays confirmation message
    // and handles submissions in case of confirmation
    delete()
}

当然,您可以将相关资源添加到同一控制器。假设您有个实体Business,每个BusinessService实体都有几个实体。一个控制器可能看起来像这样:

class BusinessController
{
    index();

    show(id);

    create();

    update(id);

    delete();

    // display the business services for a certain business
    listBusinessServices(businessId);

    // displays a certain business service
    showBusinessService(id);

    // create a new business service for a certain business
    createBusinessService(businessId);

    // updates a certain business service
    updateBusinessService(id);

    // deletes a certain business service
    deleteBusinessService(id);
}

当相关的子实体没有父实体不能存在时,这种方法很有意义。

这些是我的建议:

  • 根据一组相关的操作创建控制器(处理某些职责,例如安全性或对资源的CRUD操作等);
  • 对于基于资源的控制器,请不要添加不必要的操作(如果不应该更新资源,请不要添加更新操作);
  • 您可以添加“自定义”操作以简化操作(例如,您的Subscription实体具有基于有限数量的条目的可用性,您可以向名为的控制器添加新操作use(),其唯一目的是从中减去一个条目Subscription
  • 保持简单-不要用大量的动作和复杂的逻辑使控制器混乱,尝试通过减少动作数或设置两个控制器来简化事物;
  • 如果您使用的是关注MVC的框架,请遵循他们的最佳实践准则(如果有的话)。

一些资源可以在这里进一步阅读。


0

我看到两个对立的设计“力量”(并非控制器独有):

  • 凝聚力-控制器应将相关动作分组
  • 简单性-控制器应尽可能小以管理其复杂性

从内聚性的角度来看,这三个动作(登录,注销,注册)是相关的,但是登录和注销远不止注册。它们在语义上相关(一个是另一个的倒置),并且很可能还会使用相同的服务对象(它们的实现也是内聚的)。

我的第一个意图是将登录和注销分组到一个控制器中。但是,如果登录和注销控制器的实现不是那么简单(例如,登录具有验证码,更多身份验证方法等),那么我就可以将它们分为LoginController和LogoutController来保持简单性没有问题。这个复杂性阈值(当您应该开始拆分控制器时)所在的地方有点个人化。

还请记住,无论最初设计代码时,都可以(并且应该)在代码更改时对其进行重构。在这种情况下,从简单的设计(拥有一个AuthenticationController)开始非常典型,随着时间的流逝,您将收到更多的要求,这些要求会使代码复杂化。一旦超过复杂度阈值,则应将其重构为两个控制器。

顺便说一句,您的代码建议您使用GET请求注销用户。这是一个坏主意,因为HTTP GET应该为nullipotent(它不应修改应用程序的状态)。


0

以下是一些经验法则:

  • 按主题或主题进行组织,控制器名称为主题名称。

  • 请记住,控制器的名称将出现在URL中,对您的用户可见,因此最好对他们有意义。

在您提到的情况下(身份验证),MVC团队已经为您编写了控制器。打开Visual Studio 2013,然后单击

File / New / Project... 
Search installed templates for "ASP.NET MVC4 Web Application"
Choose "Internet Application" / OK.

AccountController.cs包含用于管理用户帐户的所有方法:

Login()
Logoff()
Register()
Disassociate()
Manage()
ExternalLogin()

因此,他们按主题“用户帐户和身份验证”进行了组织,主题名称为“帐户”。


0

术语

我认为将包含一些HTTP相关方法的类称为“控制器”是一个很大的误解。

Controller是处理请求的方法,但不是包含此类方法的类。所以index()submit()logout()是控制器。

包含此类方法的类之所以被称为“控制器”,是因为它构成一组控制器,并且扮演着“底层”命名空间的角色。用FP语言(如Haskell),它只是一个模块。优良作法是使OOP语言中的“控制器”类尽可能地保持无状态,除非引用了服务和其他程序范围的内容。

答案

整理出术语后,问题是“如何将控制器分为命名空间/模块?” 我认为答案是:单个名称空间/模块内的控制器应处理相同类型的数据。例如,UserController交易主要是与实例User类,但偶尔也会涉及到其他相关的东西,如果需要的话。

因为loginlogout和其他类似的动作大都是处理会话,它可能是最好把它们放在里面SessionController,和index控制器,它只是打印出表格,应放入LoginPageController,因为它显然与登录页面的交易。将HTML呈现和会话管理放在一个类中有点道理,这将违反SRP并可能违反其他一系列良好实践。

一般原则

当您在决定将代码放置在哪里时遇到麻烦时,请从要处理的数据(和类型)开始。


2
抱歉,这些是动作,而不是控制器:)
JK01

@ JK01这些就是您所说的。您知道这是术语。并且有一些框架将这些函数称为“控制器”(或“处理程序”),因为有很多框架没有将这些函数组织到类中,因为名称空间/模块已经足够了。您可以使用任何喜欢的术语,只是单词,但我认为使用较少的术语会更好。
scriptin
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.