This article summarizes the new application of design patterns in sweeping under the background of "continuous iteration of sweeping functions, based on the basic principles of design patterns, and gradually adopting the idea of design patterns for code and architecture optimization".
background
Scanning is an important component of Taobao's lens page. Its function has been running for a long time. In its historical code, object-oriented programming ideas are less used, and process-oriented programming is more used.
With the continuous iteration of the scan function, based on the basic principles of design patterns, we gradually adopt the idea of design patterns to optimize code and architecture. In this context, this article summarizes the new application of design patterns in sweeping.
Scan the original structure
The original structure of the scan is shown in the figure. Among them, there are many functional logics in the logic & presentation layer, and there is no good design and splitting. Here are a few examples:
The processing logic of all codes is written in the same method body, and one method is close to more than 2000 lines.
The huge code processing logic is written in the viewController, which is coupled with the UI logic.
According to the existing code design, to modify a certain code logic, all logic must be fully compiled. If you continue to use this code, the maintainability of scanning will become lower and lower.
Therefore, we need to optimize the code and architecture. The idea of optimization here is:
Understand business capabilities Understand the logic of the original code, verify the original code functions online by burying the points, etc. in uncertain places Rewrite/refactor the original code functions Write unit tests, provide test case testing & go online
Overview of scanning capabilities
The decoding ability of Sweep determines the type of codes that Sweep can process, which is called first-level classification here. Based on the first-level classification, a scan will conduct a second-level classification based on the content and type of the code. The subsequent logic is to perform corresponding processing for different secondary types, as shown in the following figure for the technical link process.
Design Patterns
Chain of Responsibility Model
In the above technical link process, the code processing process corresponds to the Big Mac logic in the original viewController. Through combing, we can see that code processing is actually a chain processing, and there is a pre- and post-dependency relationship. There are two optimization schemes. The first scheme is to disassemble it into multiple method calls in sequence; the second scheme is to refer to the idea of Apple's NSOperation independent computing unit and disassemble it into multiple code processing units. Solution 1 still does not solve the problem of the open-closed principle (open to extension, closed to modification). Option 2 is a better practice. So how to design a simple structure to implement this logic?
The characteristic of the code processing link is that the chain processing can control the order of processing, and each code processing unit has a single responsibility, so here is the first step of transformation: the chain of responsibility model.
The Chain of Responsibility pattern is a behavioral design pattern that sends requests down a chain of handlers. Once a request is received, each handler can process the request or pass it on to the next handler on the chain.
The chain of responsibility model designed in this paper consists of three parts:
The Creator that creates the data manages the processing unit
Manager processing unit
Pipeline
The three structures are shown in Fig.
Creator of data
Included functions and features:
Because the data is business-based, it is only declared as a Protocol, which is implemented by the upper layer.
Creator objects the data, and after the object is generated, self.generateDataBlock(obj, Id) starts to execute
The API code example is as follows
/// 数据产生协议 <CreatorProtocol>
@protocol TBPipelineDataCreatorDelegate <NSObject>
@property (nonatomic, copy) void(^generateDataBlock)(id data, NSInteger dataId);
@end
An example of upper-level business code is as follows
@implementation TBDataCreator
@synthesize generateDataBlock;
- (void)receiveEventWithScanResult:(TBScanResult *)scanResult
eventDelegate:(id <TBScanPipelineEventDeletate>)delegate {
//对数据做对象化
TBCodeData *data = [TBCodeData new];
data.scanResult = scanResult;
data.delegate = delegate;
NSInteger dataId = 100;
//开始执行递归
self.generateDataBlock(data, dataId);
}
@end
Manager that manages processing units
Included functions and features:
Creator who manages created data
Pipeline for managing processing units
It adopts the dot syntax that supports chaining, which is convenient for writing
The API code example is as follows
@interface TBPipelineManager : NSObject
/// 添加创建数据 Creator
- (TBPipelineManager *(^)(id<TBPipelineDataCreatorDelegate> dataCreator))addDataCreator;
/// 添加处理单元 Pipeline
- (TBPipelineManager *(^)(id<TBPipelineDelegate> pipeline))addPipeline;
/// 抛出经过一系列 Pipeline 的数据。当 Creator 开始调用 generateDataBlock 后,Pipeline 就开始执行
@property (nonatomic, strong) void(^throwDataBlock)(id data);
@end
The implementation code example is as follows
@implementation TBPipelineManager
- (TBPipelineManager *(^)(id<TBPipelineDataCreatorDelegate> dataCreator))addDataCreator {
@weakify
return ^(id<TBPipelineDataCreatorDelegate> dataCreator) {
@strongify
if (dataCreator) {
[self.dataGenArr addObject:dataCreator];
}
return self;
};
}
- (TBPipelineManager *(^)(id<TBPipelineDelegate> pipeline))addPipeline {
@weakify
return ^(id<TBPipelineDelegate> pipeline) {
@strongify
if (pipeline) {
[self.pipelineArr addObject:pipeline];
//每一次add的同时,我们做链式标记(通过runtime给每个处理加Next)
if (self.pCurPipeline) {
NSObject *cur = (NSObject *)self.pCurPipeline;
cur.tb_nextPipeline = pipeline;
}
self.pCurPipeline = pipeline;
}
return self;
};
}
- (void)setThrowDataBlock:(void (^)(id _Nonnull))throwDataBlock {
_throwDataBlock = throwDataBlock;
@weakify
//Creator的数组,依次对 Block 回调进行赋值,当业务方调用此 Block 时,就是开始处理数据的时候
[self.dataGenArr enumerateObjectsUsingBlock:^(id<TBPipelineDataCreatorDelegate> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.generateDataBlock = ^(id<TBPipelineBaseDataProtocol> data, NSInteger dataId) {
@strongify
data.dataId = dataId;
//开始递归处理数据
[self handleData:data];
};
}];
}
- (void)handleData:(id)data {
[self recurPipeline:self.pipelineArr.firstObject data:data];
}
- (void)recurPipeline:(id<TBPipelineDelegate>)pipeline data:(id)data {
if (!pipeline) {
return;
}
//递归让pipeline处理数据
@weakify
[pipeline receiveData:data throwDataBlock:^(id _Nonnull throwData) {
@strongify
NSObject *cur = (NSObject *)pipeline;
if (cur.tb_nextPipeline) {
[self recurPipeline:cur.tb_nextPipeline data:throwData];
} else {
!self.throwDataBlock?:self.throwDataBlock(throwData);
}
}];
}
@end
Processing Unit Pipeline
Included functions and features:
Because the data is business-based, it is only declared as a Protocol, which is implemented by the upper layer.
The API code example is as follows
@protocol TBPipelineDelegate <NSObject>
//如果有错误,直接抛出
- (void)receiveData:(id)data throwDataBlock:(void(^)(id data))block;
@end
An example of upper-level business code is as follows
//以A类型码码处理单元为例
@implementation TBGen3Pipeline
- (void)receiveData:(id <TBCodeDataDelegate>)data throwDataBlock:(void (^)(id data))block {
TBScanResult *result = data.scanResult;
NSString *scanType = result.resultType;
NSString *scanData = result.data;
if ([scanType isEqualToString:TBScanResultTypeA]) {
//跳转逻辑
...
//可以处理,终止递归
BlockInPipeline();
} else {
//不满足处理条件,继续递归:由下一个 Pipeline 继续处理
PassNextPipeline(data);
}
}
@end
business layer call
With the above framework and upper-level implementation, it is easy to generate a code processing management and achieve the purpose of decoupling. The code example is as follows
- (void)setupPipeline {
//创建 manager 和 creator
self.manager = TBPipelineManager.new;
self.dataCreator = TBDataCreator.new;
//创建 pipeline
TBCodeTypeAPipelie *codeTypeAPipeline = TBCodeTypeAPipelie.new;
TBCodeTypeBPipelie *codeTypeBPipeline = TBCodeTypeBPipelie.new;
//...
TBCodeTypeFPipelie *codeTypeFPipeline = TBCodeTypeFPipelie.new;
//往 manager 中链式添加 creator 和 pipeline
@weakify
self.manager
.addDataCreator(self.dataCreator)
.addPipeline(codeTypeAPipeline)
.addPipeline(codeTypeBPipeline)
.addPipeline(codeTypeFPipeline)
.throwDataBlock = ^(id data) {
@strongify
if ([self.proxyImpl respondsToSelector:@selector(scanResultDidFailedProcess:)]) {
[self.proxyImpl scanResultDidFailedProcess:data];
}
};
}
state mode
Looking back at the logic of the code display, this is an important part of our user experience optimization. The code display means that for the current frame/picture, we highlight the anchor point and jump to the identified code position. There are three cases here:
When the code is not recognized, there is no anchor point display. When a single code is recognized, the anchor point is displayed and the multi-code amount is recognized after a specified time. When the anchor point is displayed and the user clicks to see it, it involves simple The display state switching of , here leads to the second step of the transformation: state mode
The state pattern is a behavioral design pattern that changes the behavior of an object when its internal state changes, making it appear as if it changed the class to which it belongs.
The state pattern designed in this article consists of two parts:
State information StateInfo
State's base class BaseState
The structure of the two is shown in the figure
State information StateInfo
Included functions and features:
In the current context, there is only one state information flow business party can save multiple state key-value pairs, and the state executes the corresponding code logic as needed.
The declaration and implementation code examples of status information are as follows
@interface TBBaseStateInfo : NSObject {
@private
TBBaseState<TBBaseStateDelegate> *_currentState; //记录当前的 State
}
//使用当前的 State 执行
- (void)performAction;
//更新当前的 State
- (void)setState:(TBBaseState <TBBaseStateDelegate> *)state;
//获取当前的 State
- (TBBaseState<TBBaseStateDelegate> *)getState;
@end
@implementation TBBaseStateInfo
- (void)performAction {
//当前状态开始执行
[_currentState perfromAction:self];
}
- (void)setState:(TBBaseState <TBBaseStateDelegate> *)state {
_currentState = state;
}
- (TBBaseState<TBBaseStateDelegate> *)getState {
return _currentState;
}
@end
An example of upper-level business code is as follows
typedef NS_ENUM(NSInteger, TBStateType) {
TBStateTypeNormal, //空状态
TBStateTypeSingleCode, //单码展示态
TBStateTypeMultiCode, //多码展示态
};
@interface TBStateInfo : TBBaseStateInfo
//以 key-value 的方式存储业务 type 和对应的状态 state
- (void)setState:(TBBaseState<TBBaseStateDelegate> *)state forType:(TBStateType)type;
//更新 type,并执行 state
- (void)setType:(TBStateType)type;
@end
@implementation TBStateInfo
- (void)setState:(TBBaseState<TBBaseStateDelegate> *)state forType:(TBStateType)type {
[self.stateDict tb_setObject:state forKey:@(type)];
}
- (void)setType:(TBStateType)type {
id oldState = [self getState];
//找到当前能响应的状态
id newState = [self.stateDict objectForKey:@(type)];
//如果状态未发生变更则忽略
if (oldState == newState)
return;
if ([newState respondsToSelector:@selector(perfromAction:)]) {
[self setState:newState];
//转态基于当前的状态信息开始执行
[newState perfromAction:self];
}
}
@end
State's base class BaseState
Included functions and features:
The base class that defines the state declares the Protocol that the base class of the state needs to follow
The protocol is as follows, the base class is empty, and after the subclass inherits, the processing of StateInfo is realized.
@protocol TBBaseStateDelegate <NSObject>
- (void)perfromAction:(TBBaseStateInfo *)stateInfo;
@end
The code example of the upper layer (taking the single code State as an example) is as follows
@interface TBSingleCodeState : TBBaseState
@end
@implementation TBSingleCodeState
//实现 Protocol
- (void)perfromAction:(TBStateInfo *)stateAction {
//业务逻辑处理 Start
...
//业务逻辑处理 End
}
@end
business layer call
The following code generates a series of states, switching states when appropriate.
//状态初始化
- (void)setupState {
TBSingleCodeState *singleCodeState = TBSingleCodeState.new; //单码状态
TBNormalState *normalState = TBNormalState.new; //正常状态
TBMultiCodeState *multiCodeState = [self getMultiCodeState]; //多码状态
[self.stateInfo setState:normalState forType:TBStateTypeNormal];
[self.stateInfo setState:singleCodeState forType:TBStateTypeSingleCode];
[self.stateInfo setState:multiCodeState forType:TBStateTypeMultiCode];
}
//切换常规状态
- (void)processorA {
//...
[self.stateInfo setType:TBStateTypeNormal];
//...
}
//切换多码状态
- (void)processorB {
//...
[self.stateInfo setType:TBStateTypeMultiCode];
//...
}
//切换单码状态
- (void)processorC {
//...
[self.stateInfo setType:TBStateTypeSingleCode];
//...
}
It is best to write state switching code according to the state machine diagram to ensure that each state has a corresponding flow.
Secondary state→initial state↓ | State A | State B | state C |
---|---|---|---|
State A | Condition A | ... | ... |
State B | ... | ... | ... |
state C | ... | ... | ... |
proxy mode
During the development process, we will use the above image capabilities in more and more places, such as the "Taobao Photo" album and the "Scan" album, using the capabilities of decoding, code display, and code processing. Therefore, we need to encapsulate these capabilities and make them plugins so that they can be used anywhere. This leads to the third step of our transformation: the proxy mode. The proxy pattern is a structural design pattern that provides a substitute for an object or its placeholder. The proxy controls access to the original object and allows some processing before and after submitting the request to the object. The state model designed in this paper consists of two parts: the management of the proxy singleton GlobalProxy proxy ProxyHandler The two structures are shown in the figure
Proxy singleton GlobalProxy
The purpose of the singleton is mainly to reduce the repeated initialization of the agent, which can be initialized at the right time and the saved content can be emptied. The singleton pattern is all too familiar to iOSer, so I won't repeat it here.
Proxy management Handler
Maintaining an object provides the ability to add, delete, modify, and check the agent, and realize the operation of the agent.
Here, the Key that implements Key-Value is Protocol, and Value is a specific proxy.
The code example is as follows
+ (void)registerProxy:(id)proxy withProtocol:(Protocol *)protocol {
if (![proxy conformsToProtocol:protocol]) {
NSLog(@"#TBGlobalProxy, error");
return;
}
if (proxy) {
[[TBGlobalProxy sharedInstance].proxyDict setObject:proxy forKey:NSStringFromProtocol(protocol)];
}
}
+ (id)proxyForProtocol:(Protocol *)protocol {
if (!protocol) {
return nil;
}
id proxy = [[TBGlobalProxy sharedInstance].proxyDict objectForKey:NSStringFromProtocol(protocol)];
return proxy;
}
+ (NSDictionary *)proxyConfigs {
return [TBGlobalProxy sharedInstance].proxyDict;
}
+ (void)removeAll {
[TBGlobalProxy sharedInstance].proxyDict = [[NSMutableDictionary alloc] init];
}
business layer call
So no matter what the business side is, as long as it needs to use the corresponding capabilities, it only needs to read the Proxy from the singleton and implement the Protocol corresponding to the Proxy, such as some callbacks, obtaining the current context, etc., to obtain the Proxy's information. ability.
//读取 Proxy 的示例
- (id <TBScanProtocol>)scanProxy {
if (!_scanProxy) {
_scanProxy = [TBGlobalProxy proxyForProtocol:@protocol(TBScanProtocol)];
}
_scanProxy.proxyImpl = self;
return _scanProxy;
}
//写入 Proxy 的示例(解耦调用)
- (void)registerGlobalProxy {
//码处理能力
[TBGlobalProxy registerProxy:[[NSClassFromString(@"TBScanProxy") alloc] init]
withProtocol:@protocol(TBScanProtocol)];
//解码能力
[TBGlobalProxy registerProxy:[[NSClassFromString(@"TBDecodeProxy") alloc] init]
withProtocol:@protocol(TBDecodeProtocol)];
Scan the new architecture
Based on the above transformation and optimization, we have optimized the original scan-and-scan architecture: code splitting the logic & presentation layer into presentation layer, logic layer, and interface layer. In order to achieve the purpose of clear layers, clear responsibilities, and decoupling.
Summarize
The three design patterns precipitated above are used as the public capabilities of the Foundation for the scanning business, and are applied in the business logic of the lens page.
Through this reconstruction, the reusability of the code scanning capability has been improved, and the clarity of the structure and logic has brought about a reduction in maintenance costs. It is no longer necessary to search for a needle in a haystack to find problems in the code "Big Mac", which reduces the number of developer days.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。