PHP 的面向方面编程
面向方面編程(AOP)對于PHP來說是一個新的概念?,F在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?,F在,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 3 4 5 | { ????"require": { ????????"lisachenko/go-aop-php": "*" ????} } |
之后,使用 Composer 安裝 go-aop-php。在終端中運行下面命令:
| 1 2 | $ cd /your/project/folder $ php composer.phar update lisachenko/go-aop-php |
Composer 將會在之后數秒中內安裝引用的包以及需求。如果成功,你將看到類似下面的輸出:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | Loading composer repositories with package information Updating dependencies ??- Installing doctrine/common (2.3.0) ????Downloading: 100% ??- Installing andrewsville/php-token-reflection (1.3.1) ????Downloading: 100% ??- Installing lisachenko/go-aop-php (0.1.1) ????Downloading: 100% Writing lock file Generating autoload files |
在安裝完成后,你可以在你的代碼目錄中發現名為 vendor 的文件夾。Go! 庫及其需求就安裝在這。
| 1 2 3 4 5 6 7 8 9 10 11 | $ ls -l ./vendor total 20 drwxr-xr-x 3 csaba csaba 4096 Feb? 2 12:16 andrewsville -rw-r--r-- 1 csaba csaba? 182 Feb? 2 12:18 autoload.php drwxr-xr-x 2 csaba csaba 4096 Feb? 2 12:16 composer drwxr-xr-x 3 csaba csaba 4096 Feb? 2 12:16 doctrine drwxr-xr-x 3 csaba csaba 4096 Feb? 2 12:16 lisachenko $ ls -l ./vendor/lisachenko/ total 4 drwxr-xr-x 5 csaba csaba 4096 Feb? 2 12:16 go-aop-php |
整合到我們的項目
我們需要創建一個調用,介于路由/應用程序的入口點。自動裝彈機的然后自動包括類。開始吧!引用作為一個切面內核。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | use Go\Core\AspectKernel; use Go\Core\AspectContainer; class ApplicationAspectKernel extends AspectKernel { ????protected function configureAop(AspectContainer $container) { ????} ????protected function getApplicationLoaderPath() { ????} } |
例如,我創建了一個目錄,調用應用程序,然后添加一個類文件: ApplicationAspectKernel.php 。
我們開始切面擴展!AcpectKernel 類提供了基礎的方法用于完切面內核的工作。有兩個方法,我們必須知道:configureAop()用于注冊頁面特征,和 getApplicationLoaderPath() 返回自動加載程序的全路徑。
現在,一個簡單的建立一個空的 autoload.php 文件在你的程序目錄。和改變 getApplicationLoaderPath() 方法。如下:
| 1 2 3 4 5 6 7 8 9 10 | // [...] class ApplicationAspectKernel extends AspectKernel { ????// [...] ????protected function getApplicationLoaderPath() { ????????return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'; ????} } |
別擔心 autoload.php 就是這樣。我們將會填寫被省略的片段。
當我們第一次安裝 Go語言!和達到這一點我的過程中,我覺得需要運行一些代碼。所以開始構建一個小應用程序。
創建一個簡單的日志記錄器
我們的「方面」為一個簡單的日志記錄器,但在繼續我們應用的主要部分之前,有些代碼需要看一下。
創建一個最小的應用
我們的小應用是一個電子經紀人,能夠購買和出售股票。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Broker { ????private $name; ????private $id; ????function __construct($name, $id) { ????????$this->name = $name; ????????$this->id = $id; ????} ????function buy($symbol, $volume, $price) { ????????return $volume * $price; ????} ????function sell($symbol, $volume, $price) { ????????return $volume * $price; ????} } |
這些代碼非常簡單,Broker 類擁有兩個私有字段,儲存經紀人的名稱和 ID。
這個類同時提供了兩個方法,buy() 和 sell(),分別用于收購和出售股票。每個方法接受三個參數:股票標識、股票數量、每股價格。sell() 方法出售股票,并計算總收益。相應的,buy()方法購買股票并計算總支出。
考驗我們的經紀人
通過PHPUnit 測試程序,我們可以很容易的考驗我們經紀人。在應用目錄內創建一個子目錄,名為 Test,并在其中添加 BrokerTest.php 文件。并添加下面的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | require_once '../Broker.php'; class BrokerTest extends PHPUnit_Framework_TestCase { ????function testBrokerCanBuyShares() { ????????$broker = new Broker('John', '1'); ????????$this->assertEquals(500, $broker->buy('GOOGL', 100, 5)); ????} ????function testBrokerCanSellShares() { ????????$broker = new Broker('John', '1'); ????????$this->assertEquals(500, $broker->sell('YAHOO', 50, 10)); ????} } |
這個檢驗程序檢查經紀人方法的返回值。我們可以運行這個檢查程序檢驗我們的代碼,至少是不是語法正確。
添加一個自動加載器
讓我們創建一個自動加載器,在應用需要的時候加載類。這是一個簡單的加載器,基于PSR-0 autoloader.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ini_set('display_errors', true); spl_autoload_register(function($originalClassName) { ????$className = ltrim($originalClassName, '\\'); ????$fileName? = ''; ????$namespace = ''; ????if ($lastNsPos = strripos($className, '\\')) { ????????$namespace = substr($className, 0, $lastNsPos); ????????$className = substr($className, $lastNsPos + 1); ????????$fileName? = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; ????} ????$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; ????$resolvedFileName = stream_resolve_include_path($fileName); ????if ($resolvedFileName) { ????????require_once $resolvedFileName; ????} ????return (bool) $resolvedFileName; }); |
這就是我們 autoload.php 文件中的全部內容?,F在,變更 BrokerTest.php, 改引用Broker.php 為引用自動加載器 。
| 1 2 3 4 5 | require_once '../autoload.php'; class BrokerTest extends PHPUnit_Framework_TestCase { ????// [...] } |
運行 BrokerTest,驗證代碼運行情況。
連接到應用方面核心
我們最后的一件事是配置Go!.為此,我們需要連接所有的組件讓們能和諧工作。首先,創建一個php文件AspectKernelLoader.php,其代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | include __DIR__ . '/../vendor/lisachenko/go-aop-php/src/Go/Core/AspectKernel.php'; include 'ApplicationAspectKernel.php'; ApplicationAspectKernel::getInstance()->init(array( ????'autoload' => array( ????????'Go'?????????????? => realpath(__DIR__ . '/../vendor/lisachenko/go-aop-php/src/'), ????????'TokenReflection'? => realpath(__DIR__ . '/../vendor/andrewsville/php-token-reflection/'), ????????'Doctrine\\Common' => realpath(__DIR__ . '/../vendor/doctrine/common/lib/') ????), ????'appDir' => __DIR__ . '/../Application', ????'cacheDir' => null, ????'includePaths' => array(), ????'debug' => true )); |
我們需要連接所有的組件讓們能和諧工作!
這個文件位于前端控制器和自動加載器之間。他使用AOP框架初始化并在需要時調用autoload.php
第一行,我明確地載入AspectKernel.php和ApplicationAspectKernel.php,因為,要記住,在這個點我們還沒有自動加載器。
接下來的代碼段,我們調用ApplicationAspectKernel對象init()方法,并且給他傳遞了一個數列參數:
- autoload?定義了初始化AOP類庫的路徑。根據你實際的目錄機構調整為相應的值。
- appDir?引用了應用的目錄
- cacheDir?指出了緩存目錄(本例中中我們忽略緩存)。
- includePaths?對aspects的一個過濾器。我想看到所有特定的目錄,所以設置了一個空數組,以便看到所有的值。
- debug? 提供了額外的調試信息,這對開發非常有用,但是對已經要部屬的應用設置為false。
為了最后實現各個不同部分的連接,找出你工程中autoload.php自動加載所有的引用并且用AspectKernelLoader.php替換他們。在我們簡單的例子中,僅僅test文件需要修改:
| 1 2 3 4 5 6 7 | require_once '../AspectKernelLoader.php'; class BrokerTest extends PHPUnit_Framework_TestCase { // [...] } |
對大一點的工程,你會發現使用bootstrap.php作為單元測試但是非常有用;用require_once()做為autoload.php,或者我們的AspectKernelLoader.php應該在那載入。
記錄Broker的方法
創建BrokerAspect.php文件,代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use Go\Aop\Aspect; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\After; use Go\Lang\Annotation\Before; use Go\Lang\Annotation\Around; use Go\Lang\Annotation\Pointcut; use Go\Lang\Annotation\DeclareParents; class BrokerAspect implements Aspect { ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @Before("execution(public Broker->*(*))") // This is our PointCut ?????*/ ????public function beforeMethodExecution(MethodInvocation $invocation) { ????????echo "Entering method " . $invocation->getMethod()->getName() . "()\n"; ????} } |
我們在程序開始指定一些有對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():
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use Go\Core\AspectKernel; use Go\Core\AspectContainer; class ApplicationAspectKernel extends AspectKernel { ????protected function getApplicationLoaderPath() ????{ ????????return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'; ????} ????protected function configureAop(AspectContainer $container) ????{ ????????$container->registerAspect(new BrokerAspect()); ????} } |
運行測試和檢查輸出。你會看到類似下面的東西:
| 1 2 3 4 5 6 7 8 9 | PHPUnit 3.6.11 by Sebastian Bergmann. .Entering method __construct() Entering method buy() .Entering method __construct() Entering method sell() Time: 0 seconds, Memory: 5.50Mb OK (2 tests, 2 assertions) |
就這樣我們已設法讓代碼無論什么時候發生在broker上時都會執行。
查找參數和匹配@After
讓我們加入另外的方法到BrokerAspect。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // [...] class BrokerAspect implements Aspect { ????// [...] ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @After("execution(public Broker->*(*))") ?????*/ ????public function afterMethodExecution(MethodInvocation $invocation) { ????????echo "Finished executing method " . $invocation->getMethod()->getName() . "()\n"; ????????echo "with parameters: " . implode(', ', $invocation->getArguments()) . ".\n\n"; ????} } |
這個方法在一個公共方法執行后運行(注意@After匹配器)。染污我們加入另外一行來輸出用來調用方法的參數。我們的測試現在輸出:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | PHPUnit 3.6.11 by Sebastian Bergmann. .Entering method __construct() Finished executing method __construct() with parameters: John, 1. Entering method buy() Finished executing method buy() with parameters: GOOGL, 100, 5. .Entering method __construct() Finished executing method __construct() with parameters: John, 1. Entering method sell() Finished executing method sell() with parameters: YAHOO, 50, 10. Time: 0 seconds, Memory: 5.50Mb OK (2 tests, 2 assertions) |
獲得返回值并操縱運行
目前為止,我們學習了在一個方法執行的之前和之后,怎樣運行額外的代碼。當這個漂亮的實現后,如果我們無法看到方法返回了什么的話,它還不是非常有用。我們給aspect增加另一個方法,修改現有的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //[...] class BrokerAspect implements Aspect { ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @Before("execution(public Broker->*(*))") ?????*/ ????public function beforeMethodExecution(MethodInvocation $invocation) { ????????echo "Entering method " . $invocation->getMethod()->getName() . "()\n"; ????????echo "with parameters: " . implode(', ', $invocation->getArguments()) . ".\n"; ????} ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @After("execution(public Broker->*(*))") ?????*/ ????public function afterMethodExecution(MethodInvocation $invocation) { ????????echo "Finished executing method " . $invocation->getMethod()->getName() . "()\n\n"; ????} ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @Around("execution(public Broker->*(*))") ?????*/ ????public function aroundMethodExecution(MethodInvocation $invocation) { ????????$returned = $invocation->proceed(); ????????echo "method returned: " . $returned . "\n"; ????????return $returned; ????} } |
僅僅定義一個aspect是不夠的;我們需要將它注冊到AOP基礎設施。
這個新的代碼把參數信息移動到@Before方法。我們也增加了另一個特殊的@Around匹配器方法。這很整潔,因為原始的匹配方法調用被包裹于aroundMethodExecution()函數之內,有效的限制了原始的調用。在advise里,我們要調用$invocation->proceed(),以便執行原始的調用。如果你不這么做,原始的調用將不會發生。
這種包裝也允許我們操作返回值。advise返回的就是原始調用返回的。在我們的案例中,我們沒有修改任何東西,輸出應該看起來像這樣:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | PHPUnit 3.6.11 by Sebastian Bergmann. .Entering method __construct() with parameters: John, 1. method returned: Finished executing method __construct() Entering method buy() with parameters: GOOGL, 100, 5. method returned: 500 Finished executing method buy() .Entering method __construct() with parameters: John, 1. method returned: Finished executing method __construct() Entering method sell() with parameters: YAHOO, 50, 10. method returned: 500 Finished executing method sell() Time: 0 seconds, Memory: 5.75Mb OK (2 tests, 2 assertions) |
我們增加一點變化,賦以一個具體的broker一個discount。返回到測試類,寫如下的測試:
| 1 2 3 4 5 6 7 8 9 10 11 12 | require_once '../AspectKernelLoader.php'; class BrokerTest extends PHPUnit_Framework_TestCase { ????// [...] ????function testBrokerWithId2WillHaveADiscountOnBuyingShares() { ????????$broker = new Broker('Finch', '2'); ????????$this->assertEquals(80, $broker->buy('MS', 10, 10)); ????} } |
這會失敗:
| 1 2 3 4 5 6 7 8 9 10 11 12 | Time: 0 seconds, Memory: 6.00Mb There was 1 failure: 1) BrokerTest::testBrokerWithId2WillHaveADiscountOnBuyingShares Failed asserting that 100 matches expected 80. /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Test/BrokerTest.php:19 /usr/bin/phpunit:46 FAILURES! Tests: 3, Assertions: 3, Failures: 1. |
下一步,我們需要修改broker以便提供它的ID。只要像下面所示實現agetId()方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Broker { ????private $name; ????private $id; ????function __construct($name, $id) { ????????$this->name = $name; ????????$this->id = $id; ????} ????function getId() { ????????return $this->id; ????} ????// [...] } |
現在,修改aspect以調整具有ID值為2的broker的購買價格。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // [...] class BrokerAspect implements Aspect { ????// [...] ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @Around("execution(public Broker->buy(*))") ?????*/ ????public function aroundMethodExecution(MethodInvocation $invocation) { ????????$returned = $invocation->proceed(); ????????$broker = $invocation->getThis(); ????????if ($broker->getId() == 2) return $returned * 0.80; ????????return $returned; ????} } |
無需增加新的方法,只要修改aroundMethodExecution()函數。現在它正好匹配方法,稱作‘buy‘,并觸發了$invocation->getThis()。這有效的返回了原始的Broker對象,以便我們可以執行它的代碼。于是我們做到了!我們向broker要它的ID,如果ID等于2的話就提供一個折扣。測試現在通過了。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | PHPUnit 3.6.11 by Sebastian Bergmann. .Entering method __construct() with parameters: John, 1. Finished executing method __construct() Entering method buy() with parameters: GOOGL, 100, 5. Entering method getId() with parameters: . Finished executing method getId() Finished executing method buy() .Entering method __construct() with parameters: John, 1. Finished executing method __construct() Entering method sell() with parameters: YAHOO, 50, 10. Finished executing method sell() .Entering method __construct() with parameters: Finch, 2. Finished executing method __construct() Entering method buy() with parameters: MS, 10, 10. Entering method getId() with parameters: . Finished executing method getId() Finished executing method buy() Time: 0 seconds, Memory: 5.75Mb OK (3 tests, 3 assertions) |
匹配異常
我們現在可以在一個方法的開始和執行之后、繞過時,執行附加程序。但當方法拋出異常時又如何呢?
添加一個測試方法來購買大量微軟的股票:
| 1 2 3 4 | function testBuyTooMuch() { ????$broker = new Broker('Finch', '2'); ????$broker->buy('MS', 10000, 8); } |
現在,創建一個異常類。我們需要它是因為內建的異常類不能被 Go!AOP 或 PHPUnit 捕捉.
| 1 2 3 4 5 6 7 | class SpentTooMuchException extends Exception { ????public function __construct($message) { ????????parent::__construct($message); ????} } |
修改經紀人類,對大值拋出異常:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Broker { ????// [...] ????function buy($symbol, $volume, $price) { ????????$value = $volume * $price; ????????if ($value > 1000) ????????????throw new SpentTooMuchException(sprintf('You are not allowed to spend that much (%s)', $value)); ????????return $value; ????} ????// [...] } |
運行測試,確保它們產生失敗消息:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Time: 0 seconds, Memory: 6.00Mb There was 1 error: 1) BrokerTest::testBuyTooMuch Exception: You are not allowed to spend that much (80000) /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Broker.php:20 // [...] /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Broker.php:47 /home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Test/BrokerTest.php:24 /usr/bin/phpunit:46 FAILURES! Tests: 4, Assertions: 3, Errors: 1. |
現在,期待異常(在測試中),確保它們通過:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | class BrokerTest extends PHPUnit_Framework_TestCase { ????// [...] ????/** ?????* @expectedException SpentTooMuchException ?????*/ ????function testBuyTooMuch() { ????????$broker = new Broker('Finch', '2'); ????????$broker->buy('MS', 10000, 8); ????} } |
在我們的“方面”中建立一個新方法來匹配@AfterThrowing,別忘記指定 Use?Go\Lang\Annotation\AfterThrowing;
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // [...] Use Go\Lang\Annotation\AfterThrowing; class BrokerAspect implements Aspect { ????// [...] ????/** ?????* @param MethodInvocation $invocation Invocation ?????* @AfterThrowing("execution(public Broker->buy(*))") ?????*/ ????public function afterExceptionMethodExecution(MethodInvocation $invocation) { ????????echo 'An exception has happened'; ????} } |
@AfterThrowing匹配器抑制拋出的異常,并允許你去采取自己的行動。在我們的代碼中,我們簡單的顯示一個信息,但你可以做任何你的應用程序需要的事情。
最后的思考
這就是為什么我建議你小心使用“方面”。
面向方面編程就像給怪人們的新玩意兒;您可以立即看到其巨大的潛力。方面允許我們在我們的系統的不同部分引入額外的代碼,而無需修改原始代碼。當你需要實現一些通過緊耦合引用和方法調用會污染你的方法和類的模塊時,這會非常有用。
然而,這種靈活性,是有代價的:陰暗朦朧。有沒有辦法告訴如果一方面表的方法只是在尋找方法或類。例如,在我們的Broker類中執行方法時沒有跡象表明發生任何事情。這就是為什么我建議你小心使用“方面”的原因。
我們使用“方面”來給一個特定的經紀人提供折扣是誤用的一個例子。不要在一個真實的項目中這樣做。經紀人的折扣與經紀人相關;所以,在Broker類中保持這個邏輯?!胺矫妗睉撝粓绦胁恢苯雨P系到對象主要行為的任務。
樂在其中吧!
英文原文:Aspect-Oriented Programming in PHP with Go!
轉載于:https://www.cnblogs.com/rrsina/p/4054032.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的PHP 的面向方面编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决流氓软件布丁桌面、布丁压缩、值购助手
- 下一篇: 中国现代远程与继续教育网 统考 大学英