一、AOP简介
-
AOP: Aspect Oriented Programming 面向切面编程。
-
OOP:Object Oriented Programming 面向对象编程.
面向切面编程(也叫面向方面):Aspect Oriented Programming(AOP),是目前软件开发中的一个热点。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP是OOP的延续,相比传统的OOP来说,OOP的特点在于它可以很好的将系统横向分为很多个模块(比如通讯录模块,聊天模块,发现模块),每个子模块可以横向的衍生出更多的模块,用于更好的区分业务逻辑。而AOP其实相当于是纵向的分割系统模块,将每个模块里的公共部分提取出来(即那些与业务逻辑不相关的部分,如日志,用户行为等等),与业务逻辑相分离开来,从而降低代码的耦合度。
AOP是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
注意:AOP不是一种技术,实际上是编程思想。凡是符合AOP思想的技术,都可以看成是AOP的实现
二、为什么要用AOP
AOP的优势:
- 减少切面业务的开发量,“一次开发终生使用”,比如日志
- 减少代码耦合,方便复用。切面业务的代码可以独立出来,方便其他应用使用
- 提高代码review的质量,比如我可以规定某些类的某些方法才用特定的命名规范,这样review的时候就可以发现一些问题
AOP的弊端:
- 滥用AOP容易在多人集成开发时踩坑,比如自己的代码会被他人的切片处理所“劫持”
三、Spring里的AOP
- Spring的AOP是较典型的AOP使用,这里单独介绍一下
(1)通知(增强)Advice
通知定义了切面是什么以及何时使用,应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。
(2)连接点 Join point
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
(3)切点 Pointcut
切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,切点会匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称来指定这些切点。
(4)切面 Aspect
切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。
(5)引入 Introduction
引入允许我们向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。
(6)织入 Weaving
在过去我常常把织入与引入的概念混淆,我是这样来辨别的,“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。
Spring AOP的简单示例
public interface CustomerDao {
public void add();
public void update();
public void delete();
public void find();
}
public class CustomerDaoImpl implements CustomerDao {
public void add() {
System.out.println("添加客户");
}
public void update() {
System.out.println("修改客户");
}
public void delete() {
System.out.println("删除客户");
}
public void find() {
System.out.println("查询客户");
}
}
public class MyBeforeAdvice implements MethodBeforeAdvice{
/**
* method:执行的方法
* args:参数
* target:目标对象
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置增强...");
}
}
<!-- 不带有切点的切面 -->
<!-- 定义目标对象 -->
<bean id="customerDao" class="cn.yzu.spring3.demo3.CustomerDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="beforeAdvice" class="cn.yzu.spring3.demo3.MyBeforeAdvice"></bean>
<!-- Spring支持配置生成代理: -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置目标对象 -->
<property name="target" ref="customerDao"/>
<!-- 设置实现的接口 ,value中写接口的全路径 -->
<property name="proxyInterfaces" value="cn.yzu.spring3.demo3.CustomerDao"/>
<!-- 需要使用value:要的名称 -->
<property name="interceptorNames" value="beforeAdvice"/>
<!-- 强制使用CGLIB代理 -->
<property name="optimize" value="true"></property>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest3 {
@Autowired
//不可注入@Qualifier("customerDao"),必须注入代理对象.
@Qualifier("customerDaoProxy")
private CustomerDao customerDao;
@Test
public void demo1(){
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
/**
* 输出:
* 前置增强...
添加客户
前置增强...
修改客户
前置增强...
删除客户
前置增强...
查询客户
*/
}
}
四、 iOS AOP实战
实例:股票app全局换肤系统
需求背景:
产品希望整个app能够具有多个风格的UI(如:黑色经典,白色简约等),并且在不同的入口处(首页,侧边栏,我的,分时页面等)提供风格选择按键提供用户自由选择app UI风格
需求分析:
实际将需求分解后,我们发现涉及到的模块包括:首页,行情,资讯,交易,个人中心等,预估修改页面数量超过30个以上,修改控件数量超过200个以上
初期实现方式-广播模式:
- 编写风格皮肤模版(定义不同风格的颜色,图片)
- 编写风格皮肤中心(广播发起者)
- 编写不同入口触发风格切换按键以及事件绑定
- 修改涉及到风格切换的所有页面(广播接收者),用于接收风格皮肤中心发起换肤广播
实现难点:
- 涉及到换肤的页面控件数量庞大
- 必须将其所有改写为广播模式的接受者(注册通知接受,处理通知回调,注销通知接受)
- 换肤控件以及子控件页面都需在全局显式定义,方便在通知处理中引用,对代码污染巨大
view监听/移除:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vcMsgThemeChange:) name:kThemeChangeNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kThemeChangeNotification object:nil];
[super dealloc];
}
ViewController监听/移除:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vcMsgThemeChange:) name:kThemeChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vcStatusBarFrameChange:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kThemeChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
[super dealloc];
}
接受通知处理:
- (void)vcMsgThemeChange:(NSNotification *)notification
{
[super vcMsgThemeChange:notification];
CMSegmentControl *segment = self.segmentView;
segment.backgroundColor = ThemeColorWithID(420);
segment.selectionIndicatorColor = ThemeColorWithID(427);
segment.textColor = ThemeColorWithID(429);
segment.selectedTextColor = ThemeColorWithID(430);
[segment setNeedsDisplay];
_leftDateLabel.textColor = CLRINFOTEXT;
_rightDateLabel.textColor = CLRINFOTEXT;
[_exRightButton setTitleColor:ThemeColorWithID(40306) forState:UIControlStateNormal];
_exRightButton.backgroundColor = ThemeColorWithID(40514);
[_technicalButton setTitleColor:ThemeColorWithID(40306) forState:UIControlStateNormal];
_technicalButton.backgroundColor = ThemeColorWithID(40514);
[_horizonCursor setNeedsDisplay];
[_verticalCursor setNeedsDisplay];
_midFloatPanel.backgroundColor = ThemeColorWithID(425);
[_floatPanel updateColors];
_volMaPanel.backgroundColor = ThemeColorWithID(40530);
self.costDistribution.backgroundColor = ThemeColorWithID(40610);
self.costDistribution.layer.borderColor = ThemeColorWithID(40611).CGColor;
[self.costDistribution redraw];
[_klineView updateColors];
[_prevPage setImage:[UIImage theme_imageNamed:@"move_prev"] forState:UIControlStateNormal];
[_prevPage setImage:[UIImage theme_imageNamed:@"move_prev_highlight"] forState:UIControlStateHighlighted];
[_nextPage setImage:[UIImage theme_imageNamed:@"move_next"] forState:UIControlStateNormal];
[_nextPage setImage:[UIImage theme_imageNamed:@"move_next_highlight"] forState:UIControlStateHighlighted];
self.tecCycle.textColor = CLRINFOTEXT;
[_horizonBtn setBackgroundColor:ThemeColorWithID(40324)];
[_horizonBtn setTitleColor:ThemeColorWithID(40325) forState:UIControlStateNormal];
}
AOP方式实现:
- 编写风格皮肤模版(定义不同风格的颜色,图片)
- 编写AOP风格皮肤中心
- 编写不同入口触发风格切换按键以及事件绑定
- 改写控件颜色,图片设置方法名和参数
对比广播模式:
- 无需在页面中编写广播模式注册,注销,接收代码
- 无需全局显式定义换肤控件
- 代码改动量小的多
- 代码污染性,侵入小
UI修改-背景颜色
UIView *view = [[[UIView alloc] initWithFrame:self.frame] autorelease];
[view setThemeBackgroundColor:353];
UI修改-文字颜色
UILabel *label = [[[UILabel alloc]initWithFrame:self.bounds]autorelease];
[label setThemeTextColor:521];
UI修改-图片
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
[imageView setThemeImage:20];
具体实现部分代码:
DZHUIThemeManger+Passthrough.h
#import "DZHThemeDefine.h"
@interface DZHUIThemeManger (Passthrough)
-(void)bind:(NSObject *)themeObject colorChange:(dispatch_block_t)colorChange;
-(void)notifyColorChange;
@end
DZHUIThemeManger+Passthrough.m
#import "DZHUIThemeManger+Passthrough.h"
#import <libkern/OSAtomic.h>
#import "NSObject+CMDeallocating.h"
@interface NSObject (ThemeBind)
@property(nonatomic,assign)BOOL isBind;
@end
@implementation NSObject (ThemeBind)
-(void)setIsBind:(BOOL)isBind
{
objc_setAssociatedObject(self, @selector(isBind), @(isBind), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)isBind
{
return [objc_getAssociatedObject(self, @selector(isBind)) boolValue];
}
@end
@interface ThemePassthrough : NSObject
@property(nonatomic,assign)NSObject *orgin;
@property(nonatomic,retain)NSMutableArray *colorChanges;
@property(nonatomic,readonly)NSString *description;
-(instancetype)initWithOrgin:(NSObject *)orgin colorChange:(dispatch_block_t)colorChange;
-(void)execute;
-(void)searial:(dispatch_block_t)colorChange;
@end
@implementation ThemePassthrough
@dynamic description;
-(instancetype)initWithOrgin:(NSObject *)orgin colorChange:(dispatch_block_t)colorChange
{
self = [super init];
if (self) {
self.orgin = orgin;
self.orgin.isBind = YES;
dispatch_block_t innerBlock = [[colorChange copy]autorelease];
self.colorChanges = [[[NSMutableArray alloc]initWithObjects:innerBlock,nil]autorelease];
}
return self;
}
- (NSString *)description {
return [[[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self]autorelease];
}
-(void)dealloc
{
self.colorChanges = nil;
[super dealloc];
}
-(void)searial:(dispatch_block_t)colorChange;
{
@synchronized(self.colorChanges){
if (!self.orgin.themeColoring) {
[self.colorChanges removeAllObjects];
}
dispatch_block_t innerBlock = [[colorChange copy]autorelease];
[self.colorChanges addObject:innerBlock];
}
}
-(void)execute
{
NSEnumerator *enumerator =[self.colorChanges objectEnumerator];
dispatch_block_t colorChange;
while (colorChange = [enumerator nextObject])
{
colorChange();
}
}
@end
@implementation DZHUIThemeManger (Passthrough)
OSSpinLock _spinLock;
static NSMutableArray *passthoughs() {
static dispatch_once_t onceToken;
static NSMutableArray *passthoughs = nil;
dispatch_once(&onceToken, ^{
passthoughs = [[NSMutableArray alloc] init];
});
return passthoughs;
}
-(void)bind:(NSObject *)themeObject colorChange:(dispatch_block_t)colorChange
{
if (themeObject.isBind) {
[self change:themeObject colorChange:colorChange];
return;
}
OSSpinLockLock(&_spinLock);
{
ThemePassthrough *passthrough = [[[ThemePassthrough alloc]initWithOrgin:themeObject colorChange:colorChange]autorelease];
[passthoughs() addObject:passthrough];
CMLogDebug(@"passthoughs() [ADD] count is :%ld",(long)passthoughs().count);
[themeObject swizzleDeallocInsertBlockAction:^(id objSelf) {
OSSpinLockLock(&_spinLock);
{
NSUInteger index = [passthoughs() indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (ThemePassthrough *obj, NSUInteger index, BOOL *stop) {
return obj.orgin == objSelf;
}];
if (index != NSNotFound) {
[passthoughs() removeObjectAtIndex:index];
CMLogDebug(@"passthoughs() [REMOVE] count is :%ld",(long)passthoughs().count);
}
}
OSSpinLockUnlock(&_spinLock);
}];
}
OSSpinLockUnlock(&_spinLock);
}
-(void)change:(NSObject *)themeObject colorChange:(dispatch_block_t)colorChange
{
OSSpinLockLock(&_spinLock);
{
NSUInteger index = [passthoughs() indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (ThemePassthrough *obj, NSUInteger index, BOOL *stop) {
return obj.orgin == themeObject;
}];
ThemePassthrough *passthrough = passthoughs()[index];
[passthrough searial:colorChange];
}
OSSpinLockUnlock(&_spinLock);
}
-(void)notifyColorChange
{
OSSpinLockLock(&_spinLock);
{
for (ThemePassthrough *passthrough in passthoughs()) {
@autoreleasepool {
[passthrough execute];
}
}
}
OSSpinLockUnlock(&_spinLock);
}
@end
UIView+ThemeAddition.h
#import <UIKit/UIKit.h>
typedef void(^themeColorBlock)(id obj ,BOOL isThemeChange);
@interface UIView (ThemeAddition)
-(void)setThemeBackgroundColor:(NSUInteger)tid;
@end
@interface UIButton (ThemeAddition)
-(void)setThemeTitleColor:(NSUInteger)tid forState:(UIControlState)state;
@end
@interface UILabel (ThemeAddition)
-(void)setThemeTextColor:(NSUInteger)tid;
@end
@interface CALayer (ThemeAddition)
-(void)setThemeBorderColor:(NSUInteger)tid;
@end
@interface NSObject (ThemeAddition)
@property(nonatomic,readonly)BOOL themeColoring;
-(void)beginThemeColor;
-(void)endThemeColor;
-(void)themeColorGroup:(void(^)(id obj))group;
-(void)themeColorGroupEx:(themeColorBlock)group;
@end
UIView+ThemeAddition.m
#import "UIView+ThemeAddition.h"
#import "DZHThemeDefine.h"
#import "DZHUIThemeManger+Passthrough.h"
@implementation UIView (ThemeAddition)
-(void)setThemeBackgroundColor:(NSUInteger)tid
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
__block typeof(self) weak = self;
dispatch_block_t colorChange = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
weak.backgroundColor = ThemeColorWithID(tid);
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:colorChange];
colorChange();
}
@end
@implementation UIButton (ThemeAddition)
-(void)setThemeTitleColor:(NSUInteger)tid forState:(UIControlState)state;
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
__block typeof(self) weak = self;
dispatch_block_t colorChange = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
[weak setTitleColor:ThemeColorWithID(tid) forState:state];
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:colorChange];
colorChange();
}
@end
@implementation UILabel (ThemeAddition)
-(void)setThemeTextColor:(NSUInteger)tid
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
__block typeof(self) weak = self;
dispatch_block_t colorChange = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
weak.textColor = ThemeColorWithID(tid);
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:colorChange];
colorChange();
}
@end
@implementation CALayer (ThemeAddition)
-(void)setThemeBorderColor:(NSUInteger)tid;
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
__block typeof(self) weak = self;
dispatch_block_t colorChange = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
weak.borderColor = ThemeColorWithID(tid).CGColor;
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:colorChange];
colorChange();
}
@end
@implementation NSObject (ThemeAddition)
@dynamic themeColoring;
-(void)setThemeColoring:(BOOL)themeColoring
{
objc_setAssociatedObject(self, @selector(themeColoring), @(themeColoring), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)themeColoring
{
return [objc_getAssociatedObject(self, @selector(themeColoring))boolValue];
}
-(void)beginThemeColor
{
self.themeColoring = YES;
}
-(void)endThemeColor
{
self.themeColoring = NO;
}
-(void)themeColorGroup:(void(^)(id obj))group;
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
self.themeColoring = NO;
__block typeof(self) weak = self;
dispatch_block_t b = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
group(weak);
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:b];
b();
}
-(void)themeColorGroupEx:(themeColorBlock)group
{
#ifdef DEBUG
// NSArray *debugErrorCallStackSymbols = [NSThread callStackSymbols];
#endif
self.themeColoring = NO;
__block typeof(self) weak = self;
dispatch_block_t b = ^
{
#ifdef DEBUG
// CMLogDebug([debugErrorCallStackSymbols componentsJoinedByString:@"\r\n"]);
#endif
group(weak,YES);
};
[[DZHUIThemeManger sharedInstance]bind:self colorChange:b];
group(weak,NO);
}
@end
实现原理:
- 封装颜色,图片设置操作为闭包(block)并在换肤中心持久化
- 换肤时从换肤中心遍历换肤操作闭包,并执行
- 利用运行时(runtime)将控件的销毁状态和换肤操作闭包进行绑定
- 扩展控件UI方法用以支持换肤
总结
- AOP是一种思想,也是运用在不同程序架构中的解决方案
- 目的:在不改变已封装对象的结构,甚至是不改变代码的前提下,将可复用可封装的逻辑插入其中,达到降低了模块间的耦合度,同时提高系统的可维护性效果。
- 实现策略:大致分为预编译和运行时2种。
网友评论