`

使用 Go! 进行 PHP 的面向方面编程

    博客分类:
  • php
 
阅读更多

 

本文转自OSC:http://www.oschina.net/translate/aspect-oriented-programming-in-php-with-go

 

面向方面编程(AOP)对于PHP来说是一个新的概念。现在PHP对于 AOP 并没有官方支持,但有很多扩展和库实现了这个特性。本课中,我们将使用 Go! PHP library 来学习 PHP 如何进行 AOP 开发,或者在需要的时候,可以回来看一眼。

AOP简史

Aspect-Oriented programming is like a new gadget for geeks.

面向方面编程的思想在二十世纪90年代中期,于施乐帕洛阿尔托研究中心(PARC)成型。同很多有趣的新技术一样,由于缺少明确的定义,起初 AOP 备受争议。因此相关小组决定将未完成的想法公之于众,以便接受广大社区的反馈。关键问题在于“关注点分离(Separation of Concerns)”的概念。AOP 是一种可以分离关注的可行系方案。

AOP 于90年代末趋于成熟,标识为施乐 AspectJ 的发布,IBM 紧随其后,于2001年发布了 Hyper/J。现在,AOP是一种对于常用编程语言来说都是一种成熟的技术。

 

基本词汇

AOP 的核心就是“方面”,但在我们定义「方面『aspect』」之前,我们需要先讨论两个术语;「切点 『 point-cut』」和「通知advise』」。切点代表我们代码中的一个时间点,特指运行我们代码的某个时间。在切点运行代码被称为通知,结合一个活多个切点及通知的即为方面

通常,每个类都会有一个核心的行为或关注点,但有时,类可能存在次要的行为。例如,类可能会调用一个日志记录器或是通知一个观察员。因为类中的这些功能是次要的,其行为通常都是相同的。这种行为被称为“交叉关注点”;使用 AOP 可以避免。

 

PHP的各种AOP工具

Chris Peters 已经讨论过在PHP中实现 AOP 的Flow 框架。 Lithium 框架也提供了对AOP的实现。

另一个框架采用了不同的方法,创建了一个 C/C++ 编写的PHP扩展,在PHP解释器的层级上宣示着它的魔力。名为AOP PHP Extension,我会在后续文章中讨论它。

但正如我之前所言,本文将检阅Go! AOP-PHP 库。

 

安装并配置 Go!

Go! 库并未扩展;它完全由PHP编写,并为PHP5.4或更高版本使用。作为一个纯PHP库,它部署简易,即使是在不允许编译安装你自己的PHP扩展的受限及共享主机环境,也可以轻易安装。

使用 Composer 安装 Go!

Composer 是安装 PHP 包的首选方法。如果你没有使用过 Composer,你可以在Go! GitHub repository下载。

首先,将下面几行加入你的 composer.json 文件。

1 {
2     "require": {
3         "lisachenko/go-aop-php""*"
4     }
5 }

之后,使用 Composer 安装 go-aop-php。在终端中运行下面命令:

1 cd /your/project/folder
2 $ php composer.phar update lisachenko/go-aop-php

Composer 将会在之后数秒中内安装引用的包以及需求。如果成功,你将看到类似下面的输出:

01 Loading composer repositories with package information
02 Updating dependencies
03   - Installing doctrine/common (2.3.0)
04     Downloading: 100%
05  
06   - Installing andrewsville/php-token-reflection (1.3.1)
07     Downloading: 100%
08  
09   - Installing lisachenko/go-aop-php (0.1.1)
10     Downloading: 100%
11  
12 Writing lock file
13 Generating autoload files

在安装完成后,你可以在你的代码目录中发现名为 vendor 的文件夹。Go! 库及其需求就安装在这。

01 ls -l ./vendor
02 total 20
03 drwxr-xr-x 3 csaba csaba 4096 Feb  2 12:16 andrewsville
04 -rw-r--r-- 1 csaba csaba  182 Feb  2 12:18 autoload.php
05 drwxr-xr-x 2 csaba csaba 4096 Feb  2 12:16 composer
06 drwxr-xr-x 3 csaba csaba 4096 Feb  2 12:16 doctrine
07 drwxr-xr-x 3 csaba csaba 4096 Feb  2 12:16 lisachenko
08  
09 ls -l ./vendor/lisachenko/
10 total 4
11 drwxr-xr-x 5 csaba csaba 4096 Feb  2 12:16 go-aop-php

 

整合到我们的项目

我们需要创建一个调用,介于路由/应用程序的入口点。自动装弹机的然后自动包括类。开始吧!引用作为一个切面内核。

01 use Go\Core\AspectKernel;
02 use Go\Core\AspectContainer;
03  
04 class ApplicationAspectKernel extends AspectKernel {
05  
06     protected function configureAop(AspectContainer $container) {
07  
08     }
09  
10     protected function getApplicationLoaderPath() {
11  
12     }
13  
14 }
现在,AOP是一种在通用编程语言中相当成熟的技术。

例如,我创建了一个目录,调用应用程序,然后添加一个类文件: ApplicationAspectKernel.php 。

我们开始切面扩展!AcpectKernel 类提供了基础的方法用于完切面内核的工作。有两个方法,我们必须知道:configureAop()用于注册页面特征,和 getApplicationLoaderPath() 返回自动加载程序的全路径。

现在,一个简单的建立一个空的 autoload.php 文件在你的程序目录。和改变 getApplicationLoaderPath() 方法。如下:

 

01 // [...]
02 class ApplicationAspectKernel extends AspectKernel {
03  
04     // [...]
05  
06     protected function getApplicationLoaderPath() {
07         return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php';
08     }
09  
10 }

别担心 autoload.php 就是这样。我们将会填写被省略的片段。

当我们第一次安装 Go语言!和达到这一点我的过程中,我觉得需要运行一些代码。所以开始构建一个小应用程序。

 

创建一个简单的日志记录器

我们的「方面」为一个简单的日志记录器,但在继续我们应用的主要部分之前,有些代码需要看一下。

创建一个最小的应用

我们的小应用是一个电子经纪人,能够购买和出售股票。

01 class Broker {
02  
03     private $name;
04     private $id;
05  
06     function __construct($name$id) {
07         $this->name = $name;
08         $this->id = $id;
09     }
10  
11     function buy($symbol$volume$price) {
12         return $volume $price;
13     }
14  
15     function sell($symbol$volume$price) {
16         return $volume $price;
17     }
18  
19 }

这些代码非常简单,Broker 类拥有两个私有字段,储存经纪人的名称和 ID。

 

这个类同时提供了两个方法,buy() 和 sell(),分别用于收购和出售股票。每个方法接受三个参数:股票标识、股票数量、每股价格。sell() 方法出售股票,并计算总收益。相应的,buy()方法购买股票并计算总支出。

 

考验我们的经纪人

通过PHPUnit 测试程序,我们可以很容易的考验我们经纪人。在应用目录内创建一个子目录,名为 Test,并在其中添加 BrokerTest.php 文件。并添加下面的代码:

01 require_once '../Broker.php';
02  
03 class BrokerTest extends PHPUnit_Framework_TestCase {
04  
05     function testBrokerCanBuyShares() {
06         $broker new Broker('John''1');
07         $this->assertEquals(500, $broker->buy('GOOGL', 100, 5));
08     }
09  
10     function testBrokerCanSellShares() {
11         $broker new Broker('John''1');
12         $this->assertEquals(500, $broker->sell('YAHOO', 50, 10));
13     }
14  
15 }

这个检验程序检查经纪人方法的返回值。我们可以运行这个检查程序检验我们的代码,至少是不是语法正确。

 

 

添加一个自动加载器

让我们创建一个自动加载器,在应用需要的时候加载类。这是一个简单的加载器,基于PSR-0 autoloader.

01 ini_set('display_errors', true);
02  
03 spl_autoload_register(function($originalClassName) {
04     $className = ltrim($originalClassName'\\');
05     $fileName  '';
06     $namespace '';
07     if ($lastNsPos strripos($className'\\')) {
08         $namespace substr($className, 0, $lastNsPos);
09         $className substr($className$lastNsPos + 1);
10         $fileName  str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
11     }
12     $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
13  
14     $resolvedFileName = stream_resolve_include_path($fileName);
15     if ($resolvedFileName) {
16         require_once $resolvedFileName;
17     }
18     return (bool) $resolvedFileName;
19 });

这就是我们 autoload.php 文件中的全部内容。现在,变更 BrokerTest.php, 改引用Broker.php 为引用自动加载器 。

1 require_once '../autoload.php';
2  
3 class BrokerTest extends PHPUnit_Framework_TestCase {
4     // [...]
5 }

运行 BrokerTest,验证代码运行情况。

 

连接到应用方面核心

我们最后的一件事是配置Go!.为此,我们需要连接所有的组件让们能和谐工作。首先,创建一个php文件AspectKernelLoader.php,其代码如下:

01 include __DIR__ . '/../vendor/lisachenko/go-aop-php/src/Go/Core/AspectKernel.php';
02 include 'ApplicationAspectKernel.php';
03  
04 ApplicationAspectKernel::getInstance()->init(array(
05     'autoload' => array(
06         'Go'               => realpath(__DIR__ . '/../vendor/lisachenko/go-aop-php/src/'),
07         'TokenReflection'  => realpath(__DIR__ . '/../vendor/andrewsville/php-token-reflection/'),
08         'Doctrine\\Common' => realpath(__DIR__ . '/../vendor/doctrine/common/lib/')
09     ),
10     'appDir' => __DIR__ . '/../Application',
11     'cacheDir' => null,
12     'includePaths' => array(),
13     'debug' => true
14 ));

我们需要连接所有的组件让们能和谐工作!

这个文件位于前端控制器和自动加载器之间。他使用AOP框架初始化并在需要时调用autoload.php

第一行,我明确地载入AspectKernel.php和ApplicationAspectKernel.php,因为,要记住,在这个点我们还没有自动加载器。

接下来的代码段,我们调用ApplicationAspectKernel对象init()方法,并且给他传递了一个数列参数:

  • autoload 定义了初始化AOP类库的路径。根据你实际的目录机构调整为相应的值。
  • appDir 引用了应用的目录
  • cacheDir 指出了缓存目录(本例中中我们忽略缓存)。
  • includePaths 对aspects的一个过滤器。我想看到所有特定的目录,所以设置了一个空数组,以便看到所有的值。
  • debug  提供了额外的调试信息,这对开发非常有用,但是对已经要部属的应用设置为false。

为了最后实现各个不同部分的连接,找出你工程中autoload.php自动加载所有的引用并且用AspectKernelLoader.php替换他们。在我们简单的例子中,仅仅test文件需要修改:

1 require_once '../AspectKernelLoader.php';
2  
3 class BrokerTest extends PHPUnit_Framework_TestCase {
4  
5 // [...]
6  
7 }

对大一点的工程,你会发现使用bootstrap.php作为单元测试但是非常有用;用require_once()做为autoload.php,或者我们的AspectKernelLoader.php应该在那载入。

 

 

 

记录Broker的方法

创建BrokerAspect.php文件,代码如下:

01 use Go\Aop\Aspect;
02 use Go\Aop\Intercept\FieldAccess;
03 use Go\Aop\Intercept\MethodInvocation;
04 use Go\Lang\Annotation\After;
05 use Go\Lang\Annotation\Before;
06 use Go\Lang\Annotation\Around;
07 use Go\Lang\Annotation\Pointcut;
08 use Go\Lang\Annotation\DeclareParents;
09  
10 class BrokerAspect implements Aspect {
11  
12     /**
13      * @param MethodInvocation $invocation Invocation
14      * @Before("execution(public Broker->*(*))") // This is our PointCut
15      */
16     public function beforeMethodExecution(MethodInvocation $invocation) {
17         echo "Entering method " $invocation->getMethod()->getName() . "()\n";
18     }
19 }

我们在程序开始指定一些有对AOP框架有用的语句。接着,我们创建了自己的方面类叫BrokerAspect,用它实现Aspect。接着,我们指定了我们aspect的匹配逻辑。

 
1 * @Before("execution(public Broker->*(*))")
  • @Before 给出合适应用建议. 可能的参数有@Before,@After,@Around和@After线程.
  • "execution(public Broker->*(*))" 给执行一个类所有的公共方法指出了匹配规则,可以用任意数量的参数调用Broker,语法是:                  
1 [operation - execution/access]([method/attribute type - public/protected] [class]->[method/attribute]([params])

请注意匹配机制不可否认有点笨拙。你在规则的每一部分仅可以使用一个星号‘*‘。例如public Broker->匹配一个叫做Broker的类;public Bro*->匹配以Bro开头的任何类;public *ker->匹配任何ker结尾的类。

public *rok*->将匹配不到任何东西;你不能在同一个匹配中使用超过一个的星号。

紧接着匹配程序的函数会在有时间发生时调用。在本例中的方法将会在每一个Broker公共方法调用之前执行。其参数$invocation(类型为MethodInvocation)子自动传递到我们的方法的。这个对象提供了多种方式获取调用方法的信息。在第一个例子中,我们使用他获取了方法的名字,并且输出。

 

注册切面

仅仅定义一个切面是不够的;我们需要把它注册到AOP架构里。否则,它不会生效。编辑ApplicationAspectKernel.php同时在容器上的configureAop()方法里调用registerAspect():

01 use Go\Core\AspectKernel;
02 use Go\Core\AspectContainer;
03  
04 class ApplicationAspectKernel extends AspectKernel
05 {
06  
07     protected function getApplicationLoaderPath()
08     {
09         return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php';
10     }
11  
12     protected function configureAop(AspectContainer $container)
13     {
14         $container->registerAspect(new BrokerAspect());
15     }
16 }

运行测试和检查输出。你会看到类似下面的东西:

1 PHPUnit 3.6.11 by Sebastian Bergmann.
2  
3 .Entering method __construct()
4 Entering method buy()
5 .Entering method __construct()
6 Entering method sell()
7 Time: 0 seconds, Memory: 5.50Mb
8  
9 OK (2 tests, 2 assertions)

就这样我们已设法让代码无论什么时候发生在broker上时都会执行。

 

 

查找参数和匹配@After

让我们加入另外的方法到BrokerAspect。

01 // [...]
02 class BrokerAspect implements Aspect {
03  
04     // [...]
05  
06     /**
07      * @param MethodInvocation $invocation Invocation
08      * @After("execution(public Broker->*(*))")
09      */
10     public function afterMethodExecution(MethodInvocation $invocation) {
11         echo "Finished executing method " $invocation->getMethod()->getName() . "()\n";
12         echo "with parameters: " . implode(', '$invocation->getArguments()) . ".\n\n";
13     }
14 }

这个方法在一个公共方法执行后运行(注意@After匹配器)。染污我们加入另外一行来输出用来调用方法的参数。我们的测试现在输出:

01 PHPUnit 3.6.11 by Sebastian Bergmann.
02  
03 .Entering method __construct()
04 Finished executing method __construct()
05 with parameters: John, 1.
06  
07 Entering method buy()
08 Finished executing method buy()
09 with parameters: GOOGL, 100, 5.
10  
11 .Entering method __construct()
12 Finished executing method __construct()
13 with parameters: John, 1.
14  
15 Entering method sell()
16 Finished executing method sell()
17 with parameters: YAHOO, 50, 10.
18  
19 Time: 0 seconds, Memory: 5.50Mb
20  
21

OK (2 tests, 2 assertions)

 

获得返回值并操纵运行

目前为止,我们学习了在一个方法执行的之前和之后,怎样运行额外的代码。当这个漂亮的实现后,如果我们无法看到方法返回了什么的话,它还不是非常有用。我们给aspect增加另一个方法,修改现有的代码:

01 //[...]
02 class BrokerAspect implements Aspect {
03  
04     /**
05      * @param MethodInvocation $invocation Invocation
06      * @Before("execution(public Broker->*(*))")
07      */
08     public function beforeMethodExecution(MethodInvocation $invocation) {
09         echo "Entering method " $invocation->getMethod()->getName() . "()\n";
10         echo "with parameters: " . implode(', '$invocation->getArguments()) . ".\n";
11     }
12  
13     /**
14      * @param MethodInvocation $invocation Invocation
15      * @After("execution(public Broker->*(*))")
16      */
17     public function afterMethodExecution(MethodInvocation $invocation) {
18         echo "Finished executing method " $invocation->getMethod()->getName() . "()\n\n";
19     }
20  
21     /**
22      * @param MethodInvocation $invocation Invocation
23      * @Around("execution(public Broker->*(*))")
24      */
25     public function aroundMethodExecution(MethodInvocation $invocation) {
26         $returned $invocation->proceed();
27         echo "method returned: " $returned "\n";
28  
29         return $returned;
30     }
31  
32 }

仅仅定义一个aspect是不够的;我们需要将它注册到AOP基础设施。

这个新的代码把参数信息移动到@Before方法。我们也增加了另一个特殊的@Around匹配器方法。这很整洁,因为原始的匹配方法调用被包裹于aroundMethodExecution()函数之内,有效的限制了原始的调用。在advise里,我们要调用$invocation->proceed(),以便执行原始的调用。如果你不这么做,原始的调用将不会发生。

super0555
super0555
翻译于 2个月前

0人顶

 

 翻译的不错哦!

这种包装也允许我们操作返回值。advise返回的就是原始调用返回的。在我们的案例中,我们没有修改任何东西,输出应该看起来像这样:

01 PHPUnit 3.6.11 by Sebastian Bergmann.
02  
03 .Entering method __construct()
04 with parameters: John, 1.
05 method returned:
06 Finished executing method __construct()
07  
08 Entering method buy()
09 with parameters: GOOGL, 100, 5.
10 method returned: 500
11 Finished executing method buy()
12  
13 .Entering method __construct()
14 with parameters: John, 1.
15 method returned:
16 Finished executing method __construct()
17  
18 Entering method sell()
19 with parameters: YAHOO, 50, 10.
20 method returned: 500
21 Finished executing method sell()
22  
23 Time: 0 seconds, Memory: 5.75Mb
24  
25 OK (2 tests, 2 assertions)

我们增加一点变化,赋以一个具体的broker一个discount。返回到测试类,写如下的测试:

01 require_once '../AspectKernelLoader.php';
02  
03 class BrokerTest extends PHPUnit_Framework_TestCase {
04  
05     // [...]
06  
07     function testBrokerWithId2WillHaveADiscountOnBuyingShares() {
08         $broker new Broker('Finch''2');
09         $this->assertEquals(80, $broker->buy('MS', 10, 10));
10     }
11  
12 }

这会失败:

01 Time: 0 seconds, Memory: 6.00Mb
02  
03 There was 1 failure:
04  
05 1) BrokerTest::testBrokerWithId2WillHaveADiscountOnBuyingShares
06 Failed asserting that 100 matches expected 80.
07  
08 /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming inPHP/Source/Application/Test/BrokerTest.php:19
09 /usr/bin/phpunit:46
10  
11 FAILURES!
12 Tests: 3, Assertions: 3, Failures: 1.

下一步,我们需要修改broker以便提供它的ID。只要像下面所示实现agetId()方法:

01 class Broker {
02  
03     private $name;
04     private $id;
05  
06     function __construct($name$id) {
07         $this->name = $name;
08         $this->id = $id;
09     }
10  
11     function getId() {
12         return $this->id;
13     }
14  
15     // [...]
16  
17 }

现在,修改aspect以调整具有ID值为2的broker的购买价格。

01 // [...]
02 class BrokerAspect implements Aspect {
03  
04     // [...]
05  
06     /**
07      * @param MethodInvocation $invocation Invocation
08      * @Around("execution(public Broker->buy(*))")
09      */
10     public function aroundMethodExecution(MethodInvocation $invocation) {
11         $returned $invocation->proceed();
12         $broker $invocation->getThis();
13  
14         if ($broker->getId() == 2) return $returned * 0.80;
15         return $returned;
16     }
17  
18 }

无需增加新的方法,只要修改aroundMethodExecution()函数。现在它正好匹配方法,称作‘buy‘,并触发了$invocation->getThis()。这有效的返回了原始的Broker对象,以便我们可以执行它的代码。于是我们做到了!我们向broker要它的ID,如果ID等于2的话就提供一个折扣。测试现在通过了。

01 PHPUnit 3.6.11 by Sebastian Bergmann.
02  
03 .Entering method __construct()
04 with parameters: John, 1.
05 Finished executing method __construct()
06  
07 Entering method buy()
08 with parameters: GOOGL, 100, 5.
09 Entering method getId()
10 with parameters: .
11 Finished executing method getId()
12  
13 Finished executing method buy()
14  
15 .Entering method __construct()
16 with parameters: John, 1.
17 Finished executing method __construct()
18  
19 Entering method sell()
20 with parameters: YAHOO, 50, 10.
21 Finished executing method sell()
22  
23 .Entering method __construct()
24 with parameters: Finch, 2.
25 Finished executing method __construct()
26  
27 Entering method buy()
28 with parameters: MS, 10, 10.
29 Entering method getId()
30 with parameters: .
31 Finished executing method getId()
32  
33 Finished executing method buy()
34  
35 Time: 0 seconds, Memory: 5.75Mb
36  
37 OK (3 tests, 3 assertions)

 

匹配异常

我们现在可以在一个方法的开始和执行之后、绕过时,执行附加程序。但当方法抛出异常时又如何呢?

添加一个测试方法来购买大量微软的股票:

1 function testBuyTooMuch() {
2     $broker new Broker('Finch''2');
3     $broker->buy('MS', 10000, 8);
4 }

现在,创建一个异常类。我们需要它是因为内建的异常类不能被 Go!AOP 或 PHPUnit 捕捉.

1 class SpentTooMuchException extends Exception {
2  
3     public function __construct($message) {
4         parent::__construct($message);
5     }
6  
7 }

修改经纪人类,对大值抛出异常:

01 class Broker {
02  
03     // [...]
04  
05     function buy($symbol$volume$price) {
06         $value $volume $price;
07         if ($value > 1000)
08             throw newSpentTooMuchException(sprintf('You are not allowed to spend that much (%s)'$value));
09         return $value;
10     }
11  
12     // [...]
13  
14 }

运行测试,确保它们产生失败消息:

01 Time: 0 seconds, Memory: 6.00Mb
02  
03 There was 1 error:
04  
05 1) BrokerTest::testBuyTooMuch
06 Exception: You are not allowed to spend that much (80000)
07  
08 /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming inPHP/Source/Application/Broker.php:20
09 // [...]
10 /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming inPHP/Source/Application/Broker.php:47
11 /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming inPHP/Source/Application/Test/BrokerTest.php:24
12 /usr/bin/phpunit:46
13  
14 FAILURES!
15 Tests: 4, Assertions: 3, Errors: 1.

现在,期待异常(在测试中),确保它们通过:

01 class BrokerTest extends PHPUnit_Framework_TestCase {
02  
03     // [...]
04  
05     /**
06      * @expectedException SpentTooMuchException
07      */
08     function testBuyTooMuch() {
09         $broker new Broker('Finch''2');
10         $broker->buy('MS', 10000, 8);
11     }
12  
13 }

在我们的“方面”中建立一个新方法来匹配@AfterThrowing,别忘记指定 Use Go\Lang\Annotation\AfterThrowing;

01 // [...]
02 Use Go\Lang\Annotation\AfterThrowing;
03  
04 class BrokerAspect implements Aspect {
05  
06     // [...]
07  
08     /**
09      * @param MethodInvocation $invocation Invocation
10      * @AfterThrowing("execution(public Broker->buy(*))")
11      */
12     public function afterExceptionMethodExecution(MethodInvocation $invocation) {
13         echo 'An exception has happened';
14     }
15  
16 }

@AfterThrowing匹配器抑制抛出的异常,并允许你去采取自己的行动。在我们的代码中,我们简单的显示一个信息,但你可以做任何你的应用程序需要的事情。

 

最后的思考

这就是为什么我建议你小心使用“方面”。

面向方面编程就像给怪人们的新玩意儿;您可以立即看到其巨大的潜力。方面允许我们在我们的系统的不同部分引入额外的代码,而无需修改原始代码。当你需要实现一些通过紧耦合引用和方法调用会污染你的方法和类的模块时,这会非常有用。

然而,这种灵活性,是有代价的:阴暗朦胧。有没有办法告诉如果一方面表的方法只是在寻找方法或类。例如,在我们的Broker类中执行方法时没有迹象表明发生任何事情。这就是为什么我建议你小心使用“方面”的原因。

我们使用“方面”来给一个特定的经纪人提供折扣是误用的一个例子。不要在一个真实的项目中这样做。经纪人的折扣与经纪人相关;所以,在Broker类中保持这个逻辑。“方面”应该只执行不直接关系到对象主要行为的任务。

乐在其中吧!

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    PHP的AOP库Go!.zip

    是一个 PHP 5.4 库,让 PHP 支持 AOP 面向方面编程方法,无需 PECL 扩展、Runkit、evals 或者 DI 容器支持。可使用 XDebug 轻松调试。 示例代码: // Aspect/MonitorAspect.php namespace Aspect; use Go\...

    GO语言编程pdf格式高清无水印

    Go语言官方自称,之所以开发Go 语言,是因为“近10年来开发程序之难让我们有点沮丧”。 这一定位暗示了Go语言希望取代C和Java的地位,成为最流行的通用开发语言。 Go希望成为互联网时代的C语言。多数系统级语言...

    go语言编程入门学习资料

    这本书从整体的写作风格来说,会以介绍 Go 语言特性为主,示例则... 这本书面向的读者是所有打算用Go语言的开发者,主要包括目前使用C、C++、Java、C#的开发人员,甚至一些Python、PHP开发人员也可能转为 Go 程序员。

    PHP模拟测试框架AspectMock.zip

    AspectMock 不是一个普通的 PHP 模拟测试框架,通过强大的 AOP 面向方面编程理念和神奇的 Go-AOP 库, AspectMock 可以让你在 PHP 代码中进行任意的模拟测试。 示例代码: <?php function testUserCreate() { ...

    go语言编程

    编程语言已经非常多,偏性能敏感的编译型语言有 C、C++、Java、C#、Delphi和Objective-C 等,偏快速业务开发的动态解析型语言有PHP、Python、Perl、Ruby、JavaScript和Lua等,面向特 定领域的语言有Erlang、R和...

    PHP基础教程 是一个比较有价值的PHP新手教程!

    基于web的编程工作非常需要面向对象编程能力。PHP支持构造器、提取类等。 - 可伸缩性 传统上网页的交互作用是通过CGI来实现的。CGI程序的伸缩性不很理想,因为它为每一个正在运行的CGI程序开一个独立进程。解决...

    混乱:混沌编程语言

    Chaos是一种强类型的,动态的但可编译的,面向测试的过程编程语言,可实现零循环复杂度。 被影响 TypeScript的类型安全 Python的语法,模块和可扩展性 JavaScript的跨平台支持 Ruby的循环和块,Rexx的FOREVER关键字...

    GO语言程序设计

    本书以介绍Go语言特性为主,示例则尽量采用作者开发团队平常的实践... 这本书面向的读者是所有打算用Go语言的开发者,主要包括目前使用C、C++、Java、C#的开发人员,甚至一些Python、PHP开发人员也可能转为 Go 程序员。

    JAVA单例模式源码-goaop-laravel-bridge:Go的集成桥接器!AOP框架和Laravel

    面向方面的范式允许使用特殊工具扩展标准的面向对象范式,以有效解决应用程序中的横切关注点。 此代码通常存在于您的应用程序中的任何地方(例如,日志记录、缓存、监控等)并且没有 AOP 就没有简单的方法来解决这个...

    swoft::rocket:PHP微服务完整协程框架

    PHP微服务协程框架 介绍 Swoft是一个基于Swoole扩展PHP微服务协同程序框架。... 强大的面向方面的编程(AOP) 完善的容器管理,依赖注入(DI) 灵活的事件机制 基于PSR-7的HTTP消息的实现 基于PSR-14的事件管理器

    swoft-PHP

    有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。Swoft 通过长达三年的积累和方向的探索,把 Swoft 打造成...

    Hprose for php-PHP

    它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。 它提供了高效的序列化和反...

    Hprose 全名是高性能远程对象服务引擎.rar

    它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。 Hprose 支持众多流行的...

    zanphp:面向PHP开发的C10K +的高并发SOA服务和RPC服务首选框架

    一款Golang的并发编程模型实现 基于提供异步非双向I / O服务 连接池支持(内置MySQL,Redis,syslog等多种组件) 类似Golang的defer机制解决由于异常导致的资源未释放,锁未释放的问题 可继承的视图布局及组件化支持...

    2022年最新。Python教程-100天从新手到大师

    解释型语言,完美的平台可移植性动态类型语言,支持面向对象和函数式编程 ;代码规范程度高,可读性强。 Python在以下领域都有用武之地。 后端开发 - Python / Java / Go / PHP DevOps - Python / Shell / Ruby ...

    本项目是 Hprose 的 Python 版本实现 .rar

    它是一个先进的轻量级的跨语言跨平台面向对象的高性能远程动态通讯中间件。它不仅简单易用,而且功能强大。你只需要稍许的时间去学习,就能用它轻松构建跨语言跨平台的分布式应用系统了。 Hprose 支持众多编程语言...

    etcd-cr:etcd原始阅读

    etcd原始阅读 为什么有这个项目 2019其中一个目标是深度学习golang和分布式系统,...golang的面向接口编程 golang的显示,隐式调用 golang大型项目的结构和架构 raft协议的基本原理 基于raft协议的分布式系统工程实现

    jsonrpc:基于原生RPC库的HTTP JSON RPC模块

    Go语言作为面向服务端编程的语言,其标准库中自带了功能完整的RPC模块,但是这个模块用的是gob格式传输数据,当开发Go程序间通讯时是很好用的,但是要让Go之外的别的编程语言解析gob格式就很麻烦了。 夸语言通讯首选...

    matlab如何敲代码-Advanced-Python:像专业人士一样编码!

    matlab如何敲代码 所以你想成为一个百万富翁很棒...面向对象的编程:Java,C#,Eiffel 函数式编程:Haskell,Scala,Clojure,F# 面向并行/数组处理的高性能:MATLAB / Octave,Julia,R,Rust,Go 异步/事件驱动的编

    PHP中安装使用mongodb数据库

    传统数据库中,我们要操作数据库数据都要书写大量的sql语句,而且在进行无规则数据的存储时,传统关系型数据库建表时对不同字段的处理也显得有些乏力,mongo应运而生,而且ajax技术的广泛应用,json格式的广泛接受,...

Global site tag (gtag.js) - Google Analytics