一惧所、AOP簡介
AOP: Aspect Oriented Programming 面向切面編程与斤。
OOP:Object Oriented Programming 面向對象編程.
面向切面編程(也叫面向方面):Aspect Oriented Programming(AOP),是目前軟件開發(fā)中的一個熱點溉箕。利用AOP可以對業(yè)務邏輯的各個部分進行隔離壹若,從而使得業(yè)務邏輯各部分之間的耦合度降低晌姚,提高程序的可重用性沪曙,同時提高了開發(fā)的效率邑蒋。
AOP是OOP的延續(xù)姓蜂,相比傳統的OOP來說,OOP的特點在于它可以很好的將系統橫向分為很多個模塊(比如通訊錄模塊医吊,聊天模塊钱慢,發(fā)現模塊),每個子模塊可以橫向的衍生出更多的模塊卿堂,用于更好的區(qū)分業(yè)務邏輯束莫。而AOP其實相當于是縱向的分割系統模塊,將每個模塊里的公共部分提取出來(即那些與業(yè)務邏輯不相關的部分草描,如日志览绿,用戶行為等等),與業(yè)務邏輯相分離開來穗慕,從而降低代碼的耦合度饿敲。
AOP是可以通過預編譯方式和運行期動態(tài)代理實現在不修改源代碼的情況下給程序動態(tài)統一添加功能的一種技術。設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,提高代碼的靈活性和可擴展性逛绵,AOP可以說也是這種目標的一種實現怀各。
注意:AOP不是一種技術倔韭,實際上是編程思想。凡是符合AOP思想的技術瓢对,都可以看成是AOP的實現
二寿酌、為什么要用AOP
AOP的優(yōu)勢:
- 減少切面業(yè)務的開發(fā)量,“一次開發(fā)終生使用”沥曹,比如日志
- 減少代碼耦合份名,方便復用碟联。切面業(yè)務的代碼可以獨立出來妓美,方便其他應用使用
- 提高代碼review的質量,比如我可以規(guī)定某些類的某些方法才用特定的命名規(guī)范鲤孵,這樣review的時候就可以發(fā)現一些問題
AOP的弊端:
- 濫用AOP容易在多人集成開發(fā)時踩坑壶栋,比如自己的代碼會被他人的切片處理所“劫持”
三、Spring里的AOP
- Spring的AOP是較典型的AOP使用普监,這里單獨介紹一下
(1)通知(增強)Advice
通知定義了切面是什么以及何時使用贵试,應該應用在某個方法被調用之前?之后凯正?還是拋出異常時毙玻?等等。
(2)連接點 Join point
連接點是在應用執(zhí)行過程中能夠插入切面的一個點廊散。這個點可以是調用方法時桑滩,拋出異常時,甚至修改一個字段時允睹。切面代碼可以利用這些點插入到應用的正常流程中运准,并添加新的行為。
(3)切點 Pointcut
切點有助于縮小切面所通知的連接點的范圍缭受。如果說通知定義了切面的“什么”和“何時”的話胁澳,那么切點就定義了“何處”,切點會匹配通知所要織入的一個或多個連接點米者,一般常用正則表達式定義所匹配的類和方法名稱來指定這些切點韭畸。
(4)切面 Aspect
切面是通知和切點的結合。通知和切點定義了切面的全部內容——它是什么蔓搞,在何時何處完成其功能陆盘。
(5)引入 Introduction
引入允許我們向現有的類添加新方法或屬性,從而無需修改這些現有類的情況下败明,讓他們具有新的行為和狀態(tài)隘马。
(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:執(zhí)行的方法
* 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實戰(zhàn)
實例:股票app全局換膚系統
需求背景:
產品希望整個app能夠具有多個風格的UI(如:黑色經典邀泉,白色簡約等)嬉挡,并且在不同的入口處(首頁,側邊欄汇恤,我的庞钢,分時頁面等)提供風格選擇按鍵提供用戶自由選擇app UI風格
需求分析:
實際將需求分解后,我們發(fā)現涉及到的模塊包括:首頁因谎,行情基括,資訊,交易财岔,個人中心等风皿,預估修改頁面數量超過30個以上,修改控件數量超過200個以上
初期實現方式-廣播模式:
- 編寫風格皮膚模版(定義不同風格的顏色匠璧,圖片)
- 編寫風格皮膚中心(廣播發(fā)起者)
- 編寫不同入口觸發(fā)風格切換按鍵以及事件綁定
- 修改涉及到風格切換的所有頁面(廣播接收者)桐款,用于接收風格皮膚中心發(fā)起換膚廣播
實現難點:
- 涉及到換膚的頁面控件數量龐大
- 必須將其所有改寫為廣播模式的接受者(注冊通知接受,處理通知回調夷恍,注銷通知接受)
- 換膚控件以及子控件頁面都需在全局顯式定義魔眨,方便在通知處理中引用,對代碼污染巨大
view監(jiān)聽/移除:
- (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監(jiān)聽/移除:
- (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風格皮膚中心
- 編寫不同入口觸發(fā)風格切換按鍵以及事件綁定
- 改寫控件顏色冰沙,圖片設置方法名和參數
對比廣播模式:
- 無需在頁面中編寫廣播模式注冊,注銷执虹,接收代碼
- 無需全局顯式定義換膚控件
- 代碼改動量小的多
- 代碼污染性拓挥,侵入小
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)并在換膚中心持久化
- 換膚時從換膚中心遍歷換膚操作閉包袋励,并執(zhí)行
- 利用運行時(runtime)將控件的銷毀狀態(tài)和換膚操作閉包進行綁定
- 擴展控件UI方法用以支持換膚
總結
- AOP是一種思想侥啤,也是運用在不同程序架構中的解決方案
- 目的:在不改變已封裝對象的結構,甚至是不改變代碼的前提下茬故,將可復用可封裝的邏輯插入其中盖灸,達到降低了模塊間的耦合度,同時提高系統的可維護性效果磺芭。
- 實現策略:大致分為預編譯和運行時2種赁炎。