面向切面的程序設計

特性導向程式設計（Aspect-oriented programming，AOP，又譯作面向方面程序設計、面向切面程序設計、面向方面的程式設計），是電腦科學中的一種編程范型，旨在將交叉切入關注與作為業務主體的核心關注進行分離，以提高程序代碼的模塊化程度。「方面」（aspect）通過規定叫做「定點切入」（pointcut）的一種量化或查詢，在各種接合點（join point）上應用建議（advice），從而改變現有基礎代碼的行為（behavior）。比如定點切入與建議的一個實例：「對所有方法名以`set*`開頭的方法添加後台日誌」。該思想使得開發人員能夠將與代碼核心業務邏輯關係不那麼密切的功能（如日誌功能）添加至程序中，同時又不降低業務代碼的可讀性。

## 簡介

面向方面編程將代碼邏輯切分為不同的模塊（即關注，一段特定的邏輯功能）。幾乎所有的編程思想都涉及代碼功能的分類，將各項關注（concern）封裝成獨立的抽象模塊（如函數、過程、模塊、類以及方法等），後者又可供進一步實現、封裝和重寫。部分關注「交叉切入」程序代碼中的數個模塊，即在多個模塊中都有出現，它們即被稱作交叉切入關注。

日誌功能即是交叉切入關注的一個典型案例，因為日誌功能往往跨越系統中的每個業務模塊，即交叉切入（crosscut）所有有日誌需求的類及方法體。而對於一個信用卡應用程式來說，存款、取款、帳單管理是它的核心關注，日誌和持久化將成為交叉切入整個對象結構的交叉切入關注。

方面的概念源於對面向對象編程和計算反射的融合。面向方面程式語言擁有很多類似於元對象協議的功能，但有更多的限制。方面相關的編程概念包括面向主題編程、混入和委託 (計算)。使用面向方面范型的其他方式有複合過濾器和Hyper/J的hyperslices方式。

## 歷史

「面向方面編程」這一術語由施樂帕洛阿爾托研究中心的Chris Maeda首先提出，但其具體時間已經不可考證了。術語「交叉切入」（crosscut）是由Gregor Kiczales提出的。同許多重大的技術創新一樣，面向方面編程，也是在不同的地方被獨立發展出來。面向方面編程的早期工作，主要是由下面幾個機構和人員作出的：

- 施樂帕洛阿爾托研究中心：Gregor Kiczales、John Lamping、Cristina Videira Lopes等人，進行的早期工作有關於反射機制和元對象協議，在1997年Gregor Kiczales發表了論文《面向方面編程》；代表系統是基於元對象協議的面向方面編程系統和AspectJ。
- 國際商用機器公司托馬斯·J·沃森研究中心：William Harrison、Harold Ossher、Peri Tarr等人，在1980年代末進行的早期工作，有關於軟體開發環境與工具集成；後來提出了多維關注分離（MDSOC），代表系統是Hyper/J。
- 美國東北大學：Karl Lieberherr等人，進行的早期工作是研究軟體演化，提出了得墨忒耳定律、傳播模式、適應性編程；代表系統是Demeter/C++和Demeter/Java。
- 荷蘭特文特大學：Mehmet Aksit等人，其代表系統是複合過濾器。

## 基本概念

關注是對軟體工程有意義的小的、可管理的、可描述的軟體組成部分，一項關注通常只同一個特定概念或目標相關聯。傳統的程式語言，以一種線性的文本來描述軟體，只採用一種方式比如類，將軟體分解成模塊；這導致某些關注比較好的被捕捉，容易進一步組合、擴展；但還有一些關注沒有被捕捉，彌散在整個軟體內部。

關注分離（SOC）是標識、封裝和操縱只與特定概念、目標相關聯的軟體組成部分的能力，即標識、封裝和操縱關注的能力。分離關注使得解決特定領域問題的代碼從業務邏輯中獨立出來，業務邏輯的代碼中不再含有針對特定領域問題代碼的調用，業務邏輯同特定領域問題的關係通方面來封裝、維護，這樣原本分散在在整個應用程式中的變動就可以很好的管理起來。

核心關注是一個軟體最主要的關注。在傳統的程式語言中，將軟體分解成模塊的主要方式，是支配性分解，即按主關注進行模塊分解。用來描述、設計、實現一項給定關注的軟體構造單位是方法。如果兩個關注的實現的方法存在交集，則稱謂這兩個關注相互交叉切入（crosscut）。

面向方面編程的核心概念，是從核心關注中分離出交叉切入關注。面向方面編程，在支配性分解的基礎上，提供叫做方面（aspect）的一種輔助的模塊化機制，這種新的模塊化機制可以捕捉交叉切入關注。

## 接合點模型

面向方面程式語言的建議相關構件，定義了一個接合點模型（JPM）。一個JPM定義了三種東西：

- 接合點 (編程)（join point），它指定在什麼時候可以運行額外行為。在一個運行的程序中，接合點是可派上用場的能接合上額外行為的時間點。一個接合點想要有用，它必需是可尋址的，並且對普通程式設計師是可理解的。接合點還應該經歷無關緊要的程序變更而保持穩定，使得一個方面經歷這種變更而保持穩定。很多AOP實現支持方法執行和欄位引用作為接合點。
- 定點切入（pointcut），它是規定（或量化）接合點的交叉切入模式的方式。定點切入確定運行的程序在特定時間點是否匹配一個給定接合點，定點切入所專用的語言一般使用類似基礎語言的語法，例如AspectJ使用Java簽名，定點切入允許通過命名和組合來重新使用。
- 建議 (編程)（advice），這是AspectJ中的稱謂，它是指定在接合點要運行額外行為的手段。建議通常是可以在接合點之前、之後和周圍運行特定代碼。

對接合點模型進行比較可以基於：所暴露的接合點，如何規定接合點的交叉切入模式，在接合點上允許的操作，能夠表達的結構性增強機制。一些實現還支持在一個方面中定義另一個類上的一個方法。

### AspectJ的接合點模型

所有有效的Java程序也是有效的AspectJ程序，但是AspectJ容許編程者定義叫方面 (編程)的特殊構造。方面包含一些對於標準類不能獲得到的實體，它們是擴展方法、定點切入和建議。

在AspectJ中接合點 (編程)包括：方法或構造子調用或執行，一個類或對象的初始化，欄位讀或寫訪問，異常處理等。接合點不包括：循環、`super`調用，`throw`子句，多個語句等。

#### 擴展方法

擴展方法允許編程者在這個方面之內向現存的類增加方法、欄位或接口。下面的訪問者模式例子中，方面`VisitAspect`向具體元素類比如`Point`增加一個`accept`方法：

```aspectj
aspect VisitAspect {
  ⋮
  void Point.accept(Visitor v) {
    v.visit(this);
  }
  ⋮
}
```

#### 定點切入

定點切入是通過組合「原始定點切入指示符」（PCD - primitive pointcut designator）來規定接合點的交叉切入模式。定點切入可以複合和命名來重新使用。例如：

```aspectj
aspect …… {
  ⋮
  pointcut set() : execution(* set*(*)) && this(Point) && within(com.company.*);
  ⋮
}
```

這個使用名字`set()`來提及的定點切入，匹配一個方法執行接合點，如果這個方法名字開始於`set`，並且此對象（`this`）是在`com.company`包中類型`Point`的實例。

「種類」PCD匹配特定種類的接合點（比如方法執行），並且傾向於接受類似Java模樣的簽名作為輸入：

```aspectj
execution(* set*(*))
```

這個定點切入匹配一個方法執行接合點，如果這個方法名字開始於`set`，並且精確的只有一個任何類型的實際參數。

「動態」PCD檢查運行時間類型和綁定變量：

```aspectj
this(Point)
```

這個定點切入在當前執行對象是類`Point`的實例之時匹配。注意一個類的未限定名字可以通過Java的正常類型查找來使用。

「範圍」PCD限制接合點的詞法作用域：

```aspectj
within(com.company.*)
```

這個定點切入匹配在`com.company`包中任何類型的任何接合點。`*`是一種形式的通配符，它用來匹配具有一個簽名的任何東西。

#### 建議

建議 (編程)規定了在匹配了定點切入的而交叉切入一個接合點之後，除了運行接合點所指定的代碼比如一個方法之外，可以在它之前、之後或周圍運行的特定代碼。面向方面編程的運行時間系統，在某個定點切入匹配一個接合點的時候，自動調用建議。例如：

```aspectj
aspect …… {
  ⋮
  after() : set() {
    Display.update();
  }
  ⋮
}
```

這在效果上指定了：「如果匹配了`set()`定點切入而交叉切入這個接合點，在它完成之後，運行`Display.update()`。」

## 實現

下列程式語言已經實現了AOP，於語言之內或外部庫：

- .NET Framework語言（C# / VB.NET）
- PostSharp，是一個商業AOP實現，具有免費但有限制的版本。
- 微軟企業庫，提供到在核心編程領域包括數據訪問、安全性、日誌、異常處理和其他之中經過實踐檢驗的設施的API。
- ActionScript
- Ada
- AutoHotkey
- C / C++
- COBOL
- Cocoa Objective-C框架
- ColdFusion
- Common Lisp
- Delphi
- Oxygene (程式語言)
- e (驗證語言)（IEEE 1647）
- Emacs Lisp
- Groovy
- Haskell
- Java
- AspectJ
- Spring Framework
- JavaScript
- Logtalk
- Lua
- make
- Matlab
- ML
- Perl
- PHP
- Prolog
- Python
- Racket
- Ruby
- Squeak Smalltalk
- UML 2.0
- XML

## 站外連結

- Aspect-Oriented Software Development
- Aspect-Oriented Software Development（AOP年會）
- AOSD Wiki （英文維基給AOP的專欄）
- AspectJ（Java的實現）
- 更多有關於型態間成員宣告的資料
- 關於面向側面的編程和AspectJ的系列文章
- The AspectBench Compiler for AspectJ（另一個Java實現）
- 一篇深度討論AOP與AspectJ的系列文章
- 利用RemObject Taco實現AOP的文章
- Constraint-Specification Aspect Weaver
- 面向側面對物件導向：該用哪個？何時使用？
- 給Python用的AOP輕量實現
- LOOM.NET
- 剖面導向程式設計(AOP/AOSD)簡介