Is your models
directory deleted yet? If not, get it out of there! Let's create a folder within our app
directory that is simply named after our application. For this discussion, let's call our application QuickBill
, and we'll continue to use some of the interfaces and classes we've discussed before.
删掉你的models
目录了么?还没删就赶紧删了!我们将要在app
目录下创建个新的目录,目录名就以我们这个应用的名字来命名,这次我们就叫QuickBill
吧。在后续的讨论中,我们在前面写的那些接口和类都会出现。
Remember The Context 注意使用场景
Remember, if you are building a very small Laravel application, throwing a few Eloquent models in the
models
directory perfectly fine. In this chapter, we're primarily concerned with discovering more “layered” architecture suitable to large and complex projects.记住,如果你在写一个很小的Laravel应用,那在
models
目录下写几个Eloquent模型其实挺合适的。但在本章节,我们主要关注如何开发更有合适“层次”架构的大型复杂项目。
So, we should have an app/QuickBill
directory, which is at the same level in the application directory structure as controllers
and views
. We can create several more directories within QuickBill
. Let's create a Repositories
directory and a Billing
directory. Once these directories are established, remember to register them for PSR-0 auto-loading in your composer.json
file:
这样我们现在有了个app/QuickBill
目录,它和应用目录下的其他目录如controllers
还有views
都是平级的。在QuickBill
目录下我们还可以创建几个其他的目录。我们来在里面创建个Repositories
和Billing
目录。目录都创建好以后,别忘了在composer.json
文件里加入PSR-0的自动载入机制:
"autoload": {
"psr-0": {
"QuickBill": "app/"
}
}
译者注:psr-0也可以改成psr-4,
"psr-4": { "QuickBill\\": "app/QuickBill" }
psr-4是比较新的建议标准,和psr-0具体有什么区别请自行检索。
For now, let's put our Eloquent classes at the root of the QuickBill
directory. This will allow us to conveniently access them as QuickBill\User
, QuickBill\Payment
, etc. In the Repositories
folder would belongs classes such as PaymentRepository
and UserRepository
, which would contain all of our data access functions such as getRecentPayments
, and getRichestUser
. The Billing
directory would contain the classes and interfaces that work with third-party billing services like Stripe and Balanced. The folder structure would look something like this:
现在我们把继承自Eloquent的模型类都放到QuickBill
目录下面。这样我们就能很方便的以QuickBill\User
, QuickBill\Payment
的方式来使用它们。Repositories
目录属于PaymentRepository
和UserRepository
这种类,里面包含了所有对数据的访问功能比如getRecentPayments
和getRichestUser
。Billing
目录应当包含调用第三方支付服务(如Stripe和Balanced)的类。整个目录结构应该类似这样:
// app
// QuickBill
// Repositories
-> UserRepository.php
-> PaymentRepository.php
// Billing
-> BillerInterface.php
-> StripeBiller.php
// Notifications
-> BillingNotifierInterface.php
-> SmsBillingNotifier.php
User.php
Payment.php
What About Validation 数据验证怎么办?
Where to perform validation often stumps developers. Consider placing validation methods on your “entity” classes (like
User.php
andPayment.php
). Possible method name might be:validForCreation
orhasValidDomain
. Alternatively, you could create aUserValidator
class within aValidation
namespace and inject that validator into your repository. Experiment with both approaches and see what you like best!在哪儿进行数据验证常常困扰着开发人员。可以考虑将数据验证方法写进你的“实体”类里面(好比
User.php
和Payment.php
)。方法名可以设为validForCreation
或hasValidDomain
。或者你也可以专门创建个验证器类UserValidator
,放到Validation
命名空间下,然后将这个验证器类注入到你的repository类里面。两种方式你都可以试试,看哪个你更喜欢!
By just getting rid of the models
directory, you can often break down mental roadblocks to good design, allowing you to create a directory structure that is more suitable for your application. Of course, each application you build will have some similarities, since each complex application will need a data access (repository) layer, several external service layers, etc.
摆脱了models
目录后,你通常就能克服心理障碍,实现好的设计。使得你能创建一个更合适的目录结构来为你的应用服务。当然,你建立的每一个应用程序都会有一定的相似之处,因为每个复杂的应用程序都需要一个数据访问(repository)层,一些外部服务层等等。
Don't Fear Directories 别害怕目录
Don't be afraid to create more directories to organize your application. Always break your application into small components, each having a very focused responsibility. Thinking outside of the “model” box will help. For example, as we previously discussed, you could create a
Repositories
directory to hold all of your data access classes.不要惧怕建立目录来管理应用。要常常将你的应用切割成小组件,每一个组件都要有十分专注的职责。跳出“模型”的框框来思考。比如我们之前就说过,你可以创建个
Repositories
目录来存放你所有的数据访问类。
As you may have noticed, a key to solid application design is simply separating responsibilities, or creating layers of responsibility. Controllers are responsible for receiving an HTTP request and calling the proper business layer classes. Your business / domain layer is your application. It contains the classes that retrieve data, validate data, process payments, send e-mail, and any other function of your application. In fact, your domain layer doesn't need to know about “the web” at all! The web is simply a transport mechanism to access your application, and knowledge of the web and HTTP need not go beyond the routing and controller layers. Good architecture can be challenging, but will yield large profits of sustainable, clear code.
你可能注意到,优化应用的设计结构的关键就是责任划分,或者说是创建不同的责任层次。控制器只负责接收和响应HTTP请求然后调用合适的业务逻辑层的类。你的业务逻辑/领域逻辑层才是你真正的程序。你的程序包含了读取数据,验证数据,执行支付,发送电子邮件,还有你程序里任何其他的功能。事实上你的领域逻辑层不需要知道任何关于“网络”的事情!网络仅仅是个访问你程序的传输机制,关于网络和HTTP请求的一切不应该超出路由和控制器层。做出好的设计的确很有挑战性,但好的设计也会带来可持续发展的清晰的好代码。
For example, instead of accessing the web request instance in a class, you could simply pass the web input from the controller. This simple change alone decouples your class from “the web”, and the class can easily be tested without worrying about mocking a web request:
举个例子。与其在你业务逻辑类里面直接获取网络请求,不如你直接把网络请求从控制器传给你的业务逻辑类。这个简单的改动将你的业务逻辑类和“网络”分离开了,并且不必担心怎么去模拟网络请求,你的业务逻辑类就可以简单的测试了:
class BillingController extends BaseController{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
public function postCharge()
{
$this->biller->chargeAccount(Auth::user(), Input::get('amount'));
return View::make('charge.success');
}
}
Our chargeAccount
method is now much easier to test, since we no longer have to use the Request
or Input
class inside of our BillingInterface
implementation as we are simply passing the charged amount into the method as an integer.
现在chargeAccount
方法更容易测试了。 我们把Request
和Input
从BillingInterface
里提出来,然后在控制器里把方法需要的支付金额直接传过去。
Separation of responsibilities is one of the keys to writing maintainable applications. Always be asking if a given class knows more than it should. You should frequently ask yourself: “Should this class care about X?” If answer is “no”, extract the logic into another class that can be injected as a dependency.
编写拥有高可维护性应用程序的关键之一,就是责任分割。要时常检查一个类是否管得太宽。你要常常问自己“这个类需不需要关心XXX呢?”如果答案是否定的,那么把这块逻辑抽出来放到另一个类里面,然后用依赖注入的方式进行处理。(译者注:依赖注入的不同方式还记得么?调用方法传参、构造函数传参、从IoC容器获取等等。)
Single Reason To Change
A helpful method of determining whether a class has too many responsibilities is to examine your reason for changing code within that class. For example, should we need to change code within a
Biller
implementation when tweaking our notification logic? Of course not. TheBiller
implementations are concerned with billing, and should only work with notification logic via a contract. Maintaining this mindset as you are working on your code will help you quickly identify areas of an application that can be improved.如何判断一个类是否管得太宽,有一个有用的方法就是检查你为什么要改这块儿代码。举个例子:当我们想调整通知逻辑的时候,我们需要修改
Biller
的实现代码么?当然不需要,Biller
的实现仅仅需要考虑支付,它与通知逻辑应当仅通过约定来进行交互。使用这种思路过一遍代码,会让你很快找出应用中需要改进的地方。