目錄
AOP的由來
AOP的出現并不是要完全取代OOP, 而是作為OOP的補充
按照OOP的思想, 如果多個類中出現相同的代碼, 應該考慮定義一個基類, 將這些相同的代碼提取到基類中
通過引入基類實現復用的方法在大多情況下是可行的, 但是對于下面的情況卻無能為力
public class PostService {
private TransactionManager transManager;
private PerformanceMonitor pmonitor;
private TopicDao topicDao;
private ForumDao forumDao;
public void removeTopic(int topicId) {
pmonitor.start(); // ① 性能監控開始
transManager.beginTransaction(); // ② 事務處理開始
topicDao.removeTopic(topicId); // ③ 業務邏輯
transManager.commit(); // ② 事務處理結束
pmonitor.end(); // ① 性能監控結束
}
public void createForum(Forum forum) {
pmonitor.start(); // ① 性能監控開始
transManager.beginTransaction(); // ② 事務處理開始
forumDao.create(forum); // ③ 業務邏輯
transManager.commit(); // ② 事務處理結束
pmonitor.end(); // ① 性能監控結束
}
…
}
由于性能監控, 事務處理的代碼依附在業務類方法的流程中, 所以無法抽象到基類中
AOP通過橫向抽取機制, 為這類無法通過縱向繼承進行抽象的重復性代碼提供了解決方案
通過上述AOP的由來不難看出
AOP并不是"萬金油", 它一般只適合于那些具有橫切邏輯的應用場合: 如性能監測、訪問控制、事務管理、日志記錄和異常處理等
什么是AOP?
知道了為什么會有AOP這么個"東西", 那到底什么是AOP呢
百度百科中的定義如下
AOP為Aspect Oriented Programming的縮寫, 意為: 面向切面編程, 通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術
Wikipedia中的定義如下
In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification, such as "log all function calls when the function's name begins with 'set'". This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code core to the functionality. AOP forms a basis for aspect-oriented software development.
如果說百度百科的解釋比較短, 你還能看下去的話, 那么看到Wiki解釋的長度你可能就望而卻步了
站在"巨人"的肩膀上, 本人對AOP的定義如下
AOP(剖面編程, 對于面對切面編程的翻譯表示不喜歡)是指在運行時動態地將代碼切入到類的指定方法、指定位置上的編程思想
它有兩個非常關鍵的特征
不會修改接口
動態添加實現
AOP與設計模式
AOP與Bridge
對于Bridge模式
Big Four的設計模式中的定義如下
Decouple an abstraction from its implementation so that the two can vary independently
將抽象和實現解耦, 使得兩者可以獨立地變化
而上面討論AOP的第一個特點就是
- 不會修改接口
所以說, AOP體現了Bridge模式的設計思想
關于Bridge的更多介紹, 詳細參考設計模式 之 結構型模式
AOP與Dynamic Proxy
如果說AOP體現了Bridge模式的設計思想, 那么AOP的實現就要基于Dynamic Proxy了
例如下面在方法調用時打印日志的例子
final ISubject subject = new RealSubject();
ISubject proxy = (ISubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), new Class[]{ISubject.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Log.i("InvocationHandler", method.getName());
Object result = method.invoke(subject, objects);
return result;
}
});
proxy.request();
關于Dynamic Proxy的介紹, 詳細參考設計模式 之 Proxy
AOP與Spring
本文的標題是"談談移動開發編程中的AOP(剖面編程)", 為什么要說Spring呢?
啥都不說先, "一言不合"先上個Spring官方的架構圖
tutorialspoint關于AOP與Spring的描述準確而全面
為了不丟失信息和保證準確, 直接引用原文如下
One of the key components of Spring Framework is the Aspect oriented programming (AOP) framework
Aspect Oriented Programming entails breaking down program logic into distinct parts called so-called concerns. The functions that span multiple points of an application are called cross-cutting concerns and these cross-cutting concerns are conceptually separate from the application's business logic
There are various common good examples of aspects like logging, auditing, declarative transactions, security, and caching etc
如果沒有Spring的流行, 及其核心的IoC(Inversion of Control), DI(Dependecy Injection), AOP等思想, 這些概念也不會如此快如此廣地被人們熟知
更多關于Spring與AOP, 可以參考11. Aspect Oriented Programming with Spring
AOP與移動開發
Android開發中的AOP
如果要說Android中設計, 模式與AOP的集大成者, 那么非Retrofit莫屬了
關于Retrofit的詳細分析, 可以參考Retrofit分析-經典設計模式案例
首先我們先回顧下Retrofit的使用
public interface TestObjectApi {
@GET("classes/TestObject")
@Headers({
"X-LC-Id: kdWDrbX9k02QyGhLof6Injmi-gzGzoHsz",
"X-LC-Key: h2DtBuFcAd2e8NFCq5LY6V86"
})
public Call<ResponseBody> getTestObjects();
}
public class NetworkUtil {
private static OkHttpClient mOkHttpClient = new OkHttpClient();
private static Converter.Factory mFastJsonConverterFactory = FastJsonConverterFactory.create();
private static TestObjectApi mTestObjectApi;
public static TestObjectApi getTestObjectApi() {
if (mTestObjectApi == null) {
Retrofit retrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl("https://api.leancloud.cn/1.1/")
.addConverterFactory(mFastJsonConverterFactory)
.build();
mTestObjectApi = retrofit.create(TestObjectApi.class);
}
return mTestObjectApi;
}
}
這里只需要定義接口, 對象是在運行時通過Dynamic Proxy動態生成的
除了這種Dynamic Proxy的實現方法外
Dependency Injection(依賴注入)也是實現AOP的常用方式
關于依賴注入更多可以參考依賴注入原理
例如這里Retrofit.java中的client方法
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
就是將實現網絡請求的對象注入到Retrofit對象中, 這種依賴注入滿足了AOP的兩個核心特征
在接口不變的情況下, 只要是實現了規定接口的client, 都可以依賴注入到Retrofit作為實際的網絡請求對象
如果有優于OkHttp的實現的話, 完全可以自由靈活的切換到新的實現方式
如果想了解AOP在android中的應用, 可以參考Aspect Oriented Programming in Android
iOS開發中的AOP
相比于Android中AOP的兩種實現方式(Dynamic Proxy, Dependency Injection), iOS中AOP的實現就顯得有點不那么"尋常"
由于Objective-C是基于Smalltalk發展而來的"消息型"語言, 所以Objective-C相比于Java來說實現起來更加自然和簡單
沒錯, 就是基于強大的Runtime
例如在不改變系統接口和實現(當然你也沒法改變)的前提, 統計按鈕的次數
#import "UIButton+Extension.h"
#import <objc/runtime.h>
@implementation UIButton (Extension)
+ (void)load {
Class buttonClass = [UIButton class];
Method originalMethod = class_getInstanceMethod(buttonClass, @selector(sendAction:to:forEvent:));
Method swizzledMethod = class_getInstanceMethod(buttonClass, @selector(dynamic_sendAction:to:forEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)dynamic_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
NSLog(@"counter++");
[self dynamic_sendAction:action to:target forEvent:event];
}
@end
關于Runtime和Method Swizzling的更多解釋, 可以參考iOS開發 之 Runtime
很多第三方的iOS庫都是基于Runtime來實現AOP, 即在不改變接口的情況下, 動態地修改實現
而且這樣的第三方庫往往都有一個相同的特點: 不會對原有代碼做任何改動
這里我們就拿最近用到的MLeaksFinder來說吧
MLeaksFinder:精準 iOS 內存泄露檢測工具 | WeRead團隊博客中對MLeaksFinder實現分析如下
在一個ViewController被pop或dismiss一小段時間后, 看看該UIViewController, 它的view, view的subviews等等是否還存在
這里使用了AOP技術, hook掉UIViewController和UINavigationController的pop跟dismiss方法, 關于如何 hook, 請參考 Method Swizzling
參考
更多文章, 請支持我的個人博客