目录
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
参考
更多文章, 请支持我的个人博客