實(shí)際項(xiàng)目中的MVVM(積木)模式第二章:model--如何高效利用數(shù)據(jù)層

<em><strong>“不為炫技而炫技画切,不為架構(gòu)而設(shè)計(jì)架構(gòu)溺职,只為寫(xiě)出一個(gè)接地氣返劲、通俗易懂的使用方法”</strong></em>

(注:了解整個(gè)設(shè)計(jì)模式體系請(qǐng)查看我上篇文章實(shí)際項(xiàng)目中的MVVM(積木)模式:序章

這篇文章講解Model和ViewModel唉窃。

本著易擴(kuò)展、易理解的前提摩瞎,講解中Model和ViewModel都用最基礎(chǔ)的方法和易理解的思維圖拴签。

為何為何會(huì)將View和ViewModel合并在一起講解?為何我會(huì)將有些文章說(shuō)的的網(wǎng)絡(luò)層與數(shù)據(jù)層合并一起叫數(shù)據(jù)層旗们?

很簡(jiǎn)單蚓哩,我們需要明白一個(gè)道理,無(wú)論是網(wǎng)絡(luò)請(qǐng)求還是本地緩存上渴,本質(zhì)上都是傳遞數(shù)據(jù)岸梨;因此,我們要做的就是將各種來(lái)源的數(shù)據(jù)通過(guò)數(shù)據(jù)加工(ViewModel)形成統(tǒng)一的格式(Model)再通過(guò)一個(gè)統(tǒng)一的接口傳遞給需要的地方稠氮。我所講的數(shù)據(jù)層曹阔,我把這形象地叫為數(shù)據(jù)工廠。

數(shù)據(jù)工廠

首先隔披,我們先開(kāi)始說(shuō)說(shuō):Model次兆。
<strong><h4>一、Model--項(xiàng)目的信息承載與傳遞者</h4></strong>
一個(gè)多人協(xié)同開(kāi)發(fā)的項(xiàng)目锹锰,保證數(shù)據(jù)結(jié)構(gòu)的一致性和穩(wěn)定是很有必要的芥炭。而Model則是很好的實(shí)現(xiàn)了這一需求。
首先恃慧,我們先通過(guò)三個(gè)大的方面將字典與Model作一個(gè)比較园蝠,更直觀了解Model的特點(diǎn)。

<strong>1痢士、字典與模型的比較</strong>

<strong>a彪薛、取值:</strong>字典會(huì)因?yàn)闆](méi)有取值的這個(gè)key或者這個(gè)錯(cuò)誤的key剛好是這個(gè)字典中其他類(lèi)型的值對(duì)應(yīng)的key(因輸入錯(cuò)誤等原因),通過(guò)這個(gè)key取出來(lái)的值為nil或者其他類(lèi)型的值,如賦值給label之類(lèi)的文字控件怠蹂,可能會(huì)導(dǎo)致程序崩潰善延,而Model不會(huì)出現(xiàn)這樣的問(wèn)題;
<strong>b城侧、數(shù)據(jù)展示:</strong>字典無(wú)法再不改變數(shù)據(jù)源的前提下易遣,改變數(shù)據(jù)的格式,而Model則可以通過(guò)get方法實(shí)現(xiàn)這個(gè)需求嫌佑,保證了數(shù)據(jù)的原始性和可變性豆茫;
<strong>c、后期維護(hù):</strong>字典每個(gè)key的具體含義和有多少key要通過(guò)接口文檔去了解屋摇,而Model體現(xiàn)在具體的屬性和每個(gè)屬性的備注上揩魂;
可能有同學(xué)會(huì)問(wèn)建立Model一個(gè)個(gè)去復(fù)制粘貼屬性好麻煩济竹,還有像數(shù)據(jù)緩存之類(lèi)還要一個(gè)個(gè)寫(xiě)解擋歸檔好麻煩呢/(ㄒoㄒ)/~~
這么多好的優(yōu)點(diǎn)的前提下尉共,這幾個(gè)小麻煩肯定會(huì)通過(guò)方法解決噻巴席,且看下面:

<strong>2饼酿、解決Model的一些小麻煩</strong>

<strong>a荒澡、如何快速建立Model:</strong>這里有份代碼枉侧,可以將網(wǎng)絡(luò)請(qǐng)求下來(lái)的字典里的key在控制臺(tái)打印成Model里的屬性格式哦缸剪。(打印效果在代碼塊下面)

- (void)writeInfoWithDict:(NSDictionary *)dict
{
    NSMutableString *strM = [NSMutableString string];
    
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        //  NSLog(@"%@,%@",key,[obj class]);
        
        NSString *className = NSStringFromClass([obj class]) ;
        
        if ([className isEqualToString:@"__NSCFString"] | [className isEqualToString:@"__NSCFConstantString"] | [className isEqualToString:@"NSTaggedPointerString"]) {
            [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",key];
        }else if ([className isEqualToString:@"__NSCFArray"] |
                  [className isEqualToString:@"__NSArray0"] |
                  [className isEqualToString:@"__NSArrayI"]){
            [strM appendFormat:@"@property (nonatomic, strong) NSArray *%@;\\\\n",key];
        }else if ([className isEqualToString:@"__NSCFDictionary"]){
            [strM appendFormat:@"@property (nonatomic, strong) NSDictionary *%@;\\\\n",key];
        }else if ([className isEqualToString:@"__NSCFNumber"]){
            [strM appendFormat:@"@property (nonatomic, copy) NSNumber *%@;\\\\n",key];
        }else if ([className isEqualToString:@"__NSCFBoolean"]){
            [strM appendFormat:@"@property (nonatomic, assign) BOOL   %@;\\\\n",key];
        }else if ([className isEqualToString:@"NSDecimalNumber"]){
            [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
        }
        else if ([className isEqualToString:@"NSNull"]){
            [strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
        }else if ([className isEqualToString:@"__NSArrayM"]){
            [strM appendFormat:@"@property (nonatomic, strong) NSMutableArray *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
        }
        
    }];
         NSLog(@"\\\\n\\\\n%@\\\\n",strM);
}

看下圖打印效果敷鸦,那么打印出來(lái)的效果大家知道了吧白修,直接復(fù)制粘貼就OK啦:

打印效果

<strong>b妒峦、怎么解決繁瑣的解擋歸檔呢</strong>

在BaseModel(Model的基類(lèi))中寫(xiě)一個(gè)統(tǒng)一的解擋、歸檔兵睛,這里就要用到runtime中非常有用的兩個(gè)方法:

class_copyIvarList(Class cls, unsigned int *outCount) //遍歷該類(lèi)成員變量列表
ivar_getName(Ivar v) //獲取該類(lèi)某個(gè)成員變量的名字

那么具體怎么在基類(lèi)寫(xiě)一個(gè)肯骇,所有適用呢,且看下面:

- (void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        // 歸檔
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            // 解檔
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
    } 
    return self; 
}

至此祖很,所有繼承于這個(gè)Model基類(lèi)的Model都自動(dòng)實(shí)現(xiàn)了解檔歸檔的方法笛丙。
既然解決了建立Model的一些小麻煩,我們就來(lái)構(gòu)建一個(gè)項(xiàng)目中標(biāo)準(zhǔn)的BaseModel(基類(lèi)模型)假颇。

<strong>3胚鸯、何為基類(lèi)Model建立要求?</strong>

<strong>a笨鸡、數(shù)據(jù)格式讀取統(tǒng)一與寫(xiě)入統(tǒng)一姜钳;</strong>
<strong>b坦冠、模型屬性值可批量修改;</strong>

這樣才能保證在“千奇百怪哥桥、朝令夕改”的數(shù)據(jù)源中辙浑,進(jìn)入到這個(gè)項(xiàng)目體系后,面向業(yè)務(wù)工程師的時(shí)候拟糕,是統(tǒng)一整齊的標(biāo)準(zhǔn)模型判呕,然后業(yè)務(wù)工程師才會(huì)在這個(gè)基礎(chǔ)之上擴(kuò)展其他子Model。
其中讀寫(xiě)統(tǒng)一則是通過(guò)上面的解檔歸檔解決送滞,而模型屬性值批量修改則是通過(guò)李明杰大神的MJExtension(這個(gè)三方庫(kù)可以在不用繼承其他Model前提下使用侠草,保證了Model獨(dú)立性),具體代碼在BaseModel如下:

- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property
{
    if ([property.name isEqualToString:@"buy_price"]|| [property.name isEqualToString:@"buy_sale_price"]||[property.name isEqualToString:@"info_price"]||[property.name isEqualToString:@"sale_price"] ) {
        if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
            return @"價(jià)格面議";
    }//可將所有模型在生成時(shí)將涉及相應(yīng)字段的值變成“價(jià)格面議”
     else if (property.type.typeClass ==[NSString class]) {
        if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
            
            return @"--";//可將所有模型在生成時(shí)將string類(lèi)型從nil或Null變成“--”
    } else if (property.type.typeClass == [NSArray class]) {
        if (oldValue == nil)
            return [[NSArray alloc] init];//可將所有模型在生成時(shí)將array類(lèi)型從nil變成[[NSArray alloc] init]
    }
    
    return oldValue;
}

以上代碼舉了部分替換的例子犁嗅,目的是告訴大家通過(guò)這方法可以在數(shù)據(jù)源傳給客戶端是非友好數(shù)據(jù)的時(shí)候边涕,我們客戶端能夠進(jìn)行處理,給業(yè)務(wù)工程師一個(gè)友好的數(shù)據(jù)愧哟。這對(duì)數(shù)據(jù)工廠這個(gè)模式來(lái)說(shuō)是非常有必要的奥吩。
<strong>至此,BaseModel就只有寫(xiě)兩個(gè)方法:解檔與歸檔 以及 屬性值批量修改蕊梧。因?yàn)槲覀兪冀K要明白:Model是來(lái)保證整個(gè)項(xiàng)目架構(gòu)數(shù)據(jù)結(jié)構(gòu)的一致性和穩(wěn)定性霞赫。那么繼承這個(gè)BaseModel的子Model就都具備了面向業(yè)務(wù)工程師友好數(shù)據(jù)的特點(diǎn)。</strong>

那么如何正確使用子Model呢肥矢?
我舉兩個(gè)例子:

<strong>1端衰、如何將模型源數(shù)據(jù)的時(shí)間 20161010 變成 2016-10-10(正確使用get方法)</strong>

- (NSString *)pub_date{
    
    return [_pub_date formatDateString];//formatDateString是NSString的Category
    
}

這樣的好處有兩方面:一方面,業(yè)務(wù)工程師不會(huì)修改源數(shù)據(jù)甘改,保證了源數(shù)據(jù)的安全性旅东;另一方面,類(lèi)似formatDateString的方法十艾,是通過(guò)Category(也就是我后面要說(shuō)的工具類(lèi))使用的抵代,保證了代碼的低耦合性。

<strong>2忘嫉、使用子Model分離數(shù)據(jù)的一個(gè)例子(這個(gè)例子感謝我的同事 張爾柏 同學(xué)提供荤牍,展示這個(gè)例子主要目的是為了表達(dá)子Model其中一個(gè)在cell樣式數(shù)據(jù)分離的作用,可能會(huì)因?yàn)闆](méi)有demo庆冕,大家不太好理解康吵。所以,大家可以在后期demo上傳后再次詳細(xì)了解)</strong>

這里我們舉個(gè)tableView中Cell顯示(整個(gè)tableView的demo將會(huì)在View篇結(jié)束后放出)访递,其中Model要做的事情晦嵌,我們先看在Model中的代碼:

- (void)setupInfo {
    self.xibName = @"SaleInfoTableViewCell";/指定Cell的Cell樣式
    self.cellHeight = @(76);//指定Cell的高度
    self.ideltifier = @"cell";//指定Cell的ideltifier
}

通過(guò)在Model生成時(shí)執(zhí)行這個(gè)方法,實(shí)現(xiàn)了Cell樣式與樣式數(shù)據(jù)分離,做到了每個(gè)Cell的View樣式與Model的綁定惭载。

<strong>在講解Model的結(jié)尾旱函,總結(jié)起來(lái)就是:Model存在的目的是為了給業(yè)務(wù)工程師一個(gè)友好穩(wěn)定的數(shù)據(jù),讓業(yè)務(wù)工程師在相應(yīng)的模塊內(nèi)獨(dú)立地做相應(yīng)的數(shù)據(jù)操作棕兼。</strong>

<strong><h4>二陡舅、ViewModel--做一個(gè)優(yōu)秀的數(shù)據(jù)工廠</h4></strong>
如文章開(kāi)始的圖就知道,ViewModel更像一個(gè)食品工廠一樣伴挚,將不同的原料通過(guò)不同的制作工藝產(chǎn)出為統(tǒng)一的產(chǎn)品。
那么作為工廠的框架BaseViewModel應(yīng)該是怎樣的呢灾炭?
首先我們應(yīng)該先想到茎芋,我們的原料(數(shù)據(jù))來(lái)自哪里?
基本都是來(lái)自網(wǎng)絡(luò)了噻蜈出!
既然來(lái)自網(wǎng)絡(luò)田弥,那么就明確了BaseViewModel應(yīng)該實(shí)現(xiàn)三個(gè)事情:網(wǎng)絡(luò)通信、上傳铡原、下載偷厦。(都用af第三方庫(kù)實(shí)現(xiàn))
廢話不多說(shuō),直接上代碼:

<strong>1燕刻、BaseViewModel實(shí)現(xiàn)的三個(gè)方法</strong>

<strong>網(wǎng)絡(luò)請(qǐng)求:</strong>

- (void)serviceNetWorkWithUrlStr:(NSString *)urlStr//請(qǐng)求網(wǎng)絡(luò)地址
                          Params:(NSMutableDictionary *)params//請(qǐng)求參數(shù)
                         Success:(void(^)(id result))successBlock
                         Failure:(void(^)(NSError *error))failBlock {
    AFHTTPRequestOperationManager * manager = [AFHTTPRequestOperationManager manager];
    manager.requestSerializer.timeoutInterval = 10;//請(qǐng)求時(shí)長(zhǎng)
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//如果需要加解密只泼,可引入加解密的工具類(lèi)將params加密實(shí)現(xiàn)
    [manager POST:urlStr parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
        successBlock(responseObject);
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"%@", [error localizedDescription]);
        failBlock(error);
    }];
    
}

<strong>上傳:</strong>
該上傳方法需要求能同時(shí)多傳,且傳不同類(lèi)型的文件卵洗,以適應(yīng)不用的場(chǎng)景需要

- (void)uploadFileWithfileList:(NSMutableArray *)params//存放上傳文件的數(shù)組
                      Option:(NSDictionary *)optiondic//請(qǐng)求參數(shù)
                       Url:(NSString *)requestURL//上傳網(wǎng)絡(luò)地址
                         Success:(void(^)(id responseObject))successBlock
                         Failure:(void(^)(NSError *error))failBlock
                         progress:(void (^)(float progress))progress{
    AFHTTPRequestOperationManager *manager=[AFHTTPRequestOperationManager manager];
    //設(shè)置返回的數(shù)據(jù)解析格式
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/json"];
    AFHTTPRequestOperation *operation = [manager POST:requestURL parameters:optiondic constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
       //下方舉了一個(gè)極端例子:當(dāng)數(shù)組里存放了不同類(lèi)型的文件如何上傳
        for (int i = 0; i<params.count; i++) {
            if ([params[i] isKindOfClass:[UIImage class]]) {
                
                UIImage *image = [params[i] imageCompressForWidth:params[i] targetWidth:375];
                NSData *imageData =UIImagePNGRepresentation(image);
                [formData appendPartWithFileData:imageData name:@"file_content" fileName:[NSString stringWithFormat:@"anyImage_%d.png",i] mimeType:@"image/png"];
            }else if ([params[i] isKindOfClass:[NSString class]]) {
                NSURL *url = [NSURL fileURLWithPath:params[i]];
                NSData *data = [NSData dataWithContentsOfURL:url];
                [formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
                
            }else if ([params[i] isKindOfClass:[NSURL class]]) {
                NSData *data = [NSData dataWithContentsOfURL:params[i]];
                [formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
            }
            
        }
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {

        successBlock(responseObject);
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        failBlock(error);
    }];
    //獲得上傳進(jìn)度
    [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
        NSLog(@"百分比:%f",totalBytesWritten*1.0/totalBytesExpectedToWrite);
        progress(totalBytesWritten*1.0/totalBytesExpectedToWrite);
        
        
    }];
}

<strong>下載:</strong>

- (void)downloadFileWithOption:(NSDictionary *)paramDic//請(qǐng)求參數(shù)
                 withUrl:(NSString*)requestURL//下載地址
                     savedPath:(NSString*)savedPath//保存路徑
               downloadSuccess:(void (^)(id responseObject))success
               downloadFailure:(void (^)(NSError *error))failure
                      progress:(void (^)(float progress))progress

{
    AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
    NSMutableURLRequest *request =[serializer requestWithMethod:@"POST" URLString:requestURL parameters:paramDic error:nil];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
    [operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];
    [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
        float p = (float)totalBytesRead / totalBytesExpectedToRead;
        progress(p);//下載進(jìn)度
        NSLog(@"download:%f", (float)totalBytesRead / totalBytesExpectedToRead);
        
    }];
    
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        success(responseObject);
        NSLog(@"下載成功");
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        failure(error);
        
        NSLog(@"下載失敗");
        
    }];
    
    [operation start];
    
    
}

<strong>2.如何寫(xiě)好一個(gè)子ViewModel</strong>

不知道大家注意到?jīng)]有请唱,其實(shí)我們真實(shí)的項(xiàng)目中,網(wǎng)絡(luò)請(qǐng)求返回來(lái)的狀態(tài)其實(shí)可能是會(huì)存在三種狀態(tài)的:<strong>請(qǐng)求成功过蹂;請(qǐng)求失敗十绑,服務(wù)器返回錯(cuò)誤信息;請(qǐng)求失敗酷勺,網(wǎng)絡(luò)不通本橙。</strong>同時(shí),<strong>我們會(huì)在某些地方做緩存讀寫(xiě)</strong>脆诉。那既然如此甚亭,繼承BaseViewModel的子ViewModel的對(duì)外接口(<strong>唯一對(duì)外接口</strong>)應(yīng)是如下所寫(xiě):

- (void)serviceNetWorkMessageListWith
                             Success:(void(^)(id responseObject))successBlock//請(qǐng)求成功回調(diào)
                         ReceiveFail:(void (^)(id responseObject))ReceiveFailBolck//請(qǐng)求失敗,服務(wù)器返回錯(cuò)誤信息
                             Failure:(void(^)(NSError *error))failBlock//請(qǐng)求失敗库说,網(wǎng)絡(luò)不通 {
    NSMutableDictionary *tempDic = [@{@"service_code":Message_Service_Code} mutableCopy];
    [tempDic  addEntriesFromDictionary:[self getPrivateParameters]]//所有接口的公共訪問(wèn)參數(shù);
    [tempDic addEntriesFromDictionary:@{"":""}//對(duì)應(yīng)接口的對(duì)應(yīng)參數(shù)];
//這里舉個(gè)Archive做數(shù)據(jù)緩存的讀取與存儲(chǔ)的例子狂鞋,方便大家理解在某些場(chǎng)景下需要某個(gè)ViewModel作緩存時(shí),如何只對(duì)外提供一個(gè)數(shù)據(jù)接口潜的,里面作多級(jí)緩存的原理骚揍,具體實(shí)現(xiàn)大家可以結(jié)合自身優(yōu)化,比如數(shù)據(jù)庫(kù)的緩存,比如在這個(gè)類(lèi)單獨(dú)歸一個(gè)方法信不。
if ([[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code]) {
                NSArray *priarr = [[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code];
                NSMutableArray *priMuarr = [NSMutableArray arrayWithCapacity:0];
                for (NSData *data in priarr) {
                    
                    MessageInfoModel *message =  [NSKeyedUnarchiver unarchiveObjectWithData:data];//解檔
                    [priMuarr addObject:message];
                }
                successBlock(priMuarr);//返回緩存數(shù)據(jù)
            }
    [self serviceNetWorkWithUrlStr:[UrlGenerator queryNewMessageUrl] Params:tempDic Success:^(id responseObject) {

 if ([responseObject[@"code"] isEqualToString:@"0000"]) {
            
            
            NSArray *array = [MessageInfoModel mj_objectArrayWithKeyValuesArray:responseObject[@"data"]];
            NSMutableArray *enArr = [NSMutableArray arrayWithCapacity:0];
            for (MessageInfoModel *messageModel in array) {
                
                
                NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageModel];
                [enArr addObject:data];//歸檔
                
                
            }
            [[NSUserDefaults standardUserDefaults] setObject:enArr forKey:Message_Service_Code];
            successBlock(array);//返回網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
            
            
        }else {
            
            ReceiveFailBolck(responseObject[@"msg"]);
        }
        
        
    } Failure:^(NSError *error) {
        failBlock(error);
    }];
}

<strong>3嘲叔、到底ViewModel放在哪里合適?創(chuàng)建多少個(gè)ViewModel合適抽活?</strong>

開(kāi)門(mén)見(jiàn)山直說(shuō)硫戈,個(gè)人認(rèn)為只要涉及數(shù)據(jù)的View的模塊,最理想的情況應(yīng)該一個(gè)View模塊一個(gè)ViewModel下硕。
因?yàn)槎∈牛挥羞@樣,在后期項(xiàng)目越來(lái)越龐大梭姓,維護(hù)的人員越來(lái)越多的時(shí)候霜幼,才能保證模塊之間的絕對(duì)獨(dú)立。
(如果是一兩個(gè)人開(kāi)發(fā)的中小型項(xiàng)目誉尖,也沒(méi)必要一個(gè)模塊對(duì)一個(gè)ViewModel罪既,本身功能不多,沒(méi)有必要铡恕。這時(shí)可以一個(gè)Controller對(duì)一個(gè)ViewModel,或者幾個(gè)同需求功能的Controller對(duì)一個(gè)ViewModel琢感。項(xiàng)目死的,人是靈活的探熔,具體情況具體分析驹针。)

我舉兩個(gè)例子:

a.某個(gè)模塊因?yàn)榫S護(hù)人員頻次多,需求改動(dòng)頻繁祭刚,到最后需要大改某個(gè)模塊的時(shí)候牌捷,因?yàn)橹暗闹辽系较拢〝?shù)據(jù)到界面)的絕對(duì)獨(dú)立,可以完全抽出來(lái)涡驮,重新制作一個(gè)新的模塊暗甥,重新替換進(jìn)去;
b.某些模塊需要數(shù)據(jù)緩存捉捅,某些模塊又需要網(wǎng)絡(luò)狀態(tài)判斷(含大圖片展示的模塊),完全可以針對(duì)不同情況針對(duì)這些相應(yīng)模塊對(duì)應(yīng)的ViewModel數(shù)據(jù)工廠做相應(yīng)的事情撤防。
以上兩個(gè)例子,我想做過(guò)項(xiàng)目的棒口,尤其是遇到頻繁更改需求的寄月,應(yīng)該是深有體會(huì)(哎,我本人就特別有體會(huì)/(ㄒoㄒ)/~~)无牵。


借用一位大神的話結(jié)束Model篇:

<em><strong>“你必須得清楚你要做什么漾肮,業(yè)務(wù)方希望要什么。而不是為了架構(gòu)而架構(gòu)茎毁,也不是為了體驗(yàn)新技術(shù)而改架構(gòu)方案克懊。以前是MVC忱辅,最近流行MVVM,如果過(guò)去的MVC是個(gè)好架構(gòu)谭溉,沒(méi)什么特別大的缺陷墙懂,就不要推倒然后搞成MVVM“缒睿”</em></strong>

后面幾天我在各位大神的建議下會(huì)不斷優(yōu)化文章各個(gè)細(xì)節(jié)损搬,歡迎多多關(guān)注,共同學(xué)習(xí)柜与。幾天后會(huì)發(fā)出<strong> view--業(yè)務(wù)與界面結(jié)合的模塊開(kāi)發(fā) </strong>請(qǐng)多關(guān)注巧勤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市旅挤,隨后出現(xiàn)的幾起案子踢关,更是在濱河造成了極大的恐慌,老刑警劉巖粘茄,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異秕脓,居然都是意外死亡柒瓣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)吠架,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芙贫,“玉大人,你說(shuō)我怎么就攤上這事傍药』瞧剑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵拐辽,是天一觀的道長(zhǎng)拣挪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)俱诸,這世上最難降的妖魔是什么菠劝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮睁搭,結(jié)果婚禮上赶诊,老公的妹妹穿的比我還像新娘。我一直安慰自己园骆,他們只是感情好舔痪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著锌唾,像睡著了一般锄码。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天巍耗,我揣著相機(jī)與錄音秋麸,去河邊找鬼。 笑死炬太,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涩赢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了悬秉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搪柑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后垒拢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屹耐,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡按灶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年铺罢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吉殃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抱完。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撮胧,死狀恐怖老翘,靈堂內(nèi)的尸體忽然破棺而出芹啥,到底是詐尸還是另有隱情锻离,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布墓怀,位于F島的核電站汽纠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏傀履。R本人自食惡果不足惜虱朵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望啤呼。 院中可真熱鬧卧秘,春花似錦、人聲如沸官扣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惕蹄。三九已至蚯涮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卖陵,已是汗流浹背遭顶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泪蔫,地道東北人棒旗。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像撩荣,于是被迫代替她去往敵國(guó)和親铣揉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容