2013年5月29日水曜日

データソースの抽象化

はじめまして、先週からスバコラボでお世話になっています。
好きなものはお酒、嫌いなものはプログラミングです。

手があいて暇なので
データソースの抽象化について書いてみます。

よくあるパターン

webAPIからデータを拾って表示するネイティブアプリ。
フォーマットもURLも決まってなくてモックが作れない
結合のときにやたらばたばたする。
APIがミスってるのかアプリがミスってるのか分かりにくい。
デザイン確認したいだけなのにAPIがバグっててアプリが落ちる

よくありますね。

このあたりをなんとかするために
モデルからデータソースを抽象化してみます。

目標

データソースの実装に引きずられないように

今回のサンプル

記事(Article)一覧をデータソースから読み込んで表示する

データソースを抽象化

抽象化っていうと小難しそうですが、プログラミング的には
インタフェースをきって
インタフェース経由の呼び出しにするっていうことになります。




にします。

そして開発用の実装も入れてみます

コード

上の図をコードに落とすとこんな感じです。
各実装(RemoteDataSource, DebugDataSource)ははしょります。

IDataSource.h


#import 
#import "ArticleList.h"
#import "User.h"

#define ONE_PAGE_ARTICLE 10

@protocol IDataSource 

// load ====================
-(void) reloadArticleList:(ArticleList*)articleList;
-(BOOL) moreArticleList:(ArticleList*)articleList;

// edit ===================
-(void) readArticle:(Article*)article;

// create ==================
-(User*) createUser;

@end

DataSourceFactory.h

#import 
#import "IDataSource.h"
#import "DebugDataSource.h"
#import "RemoteDataSource.h"
#import "DBDataSource.h"

@interface DataSourceFactory : NSObject{
}

+(NSObject*) createDataSource;

// 開発中はpublic
+(NSObject*) createDebugDataSource;
+(NSObject*) createDbDataSource;
+(NSObject*) createRemoteDataSource;

@end

ModelManager.m


#import "ModelManager.h"
@implementation ModelManager

// typedef void (^ModelManagerCallback_t)();
// typedef void (^ModelManagerCallbackMore_t)(BOOL hasMore);

〜略〜

-(void) initialize{
 // NSObject* datasource
    datasource = [DataSourceFactory createDataSource];
    articleList = [[ArticleList alloc] init];
}

-(void) reloadArticleList:(ModelManagerCallback_t)callback{
    [SLThreadUtil threadBlock:^{
        [datasource reloadArticleList:articleList];
        [SLThreadUtil mainBlock:callback];
    }];
}

-(void) moreArticleList:(ModelManagerCallbackMore_t)callback{
    [SLThreadUtil threadBlock:^{
        BOOL hasMore = [datasource moreArticleList:articleList];
        [SLThreadUtil mainBlock:^{
            callback(hasMore);
        }];
    }];
}

-(void) readArticle:(Article*)article{
    [SLThreadUtil threadBlock:^{
        [datasource readArticle:article];
    }];
}

〜略〜
@end

Controller.m

〜略〜

[[ModelManager getInstance] reloadArticleList:^{
    [articleListViewController reload];
    [articleListViewController hasMore];
}];

[[ModelManager getInstance] moreArticleList:^(BOOL hasMore) {
    if(hasMore){
        [articleListViewController hasMore];
    }else{
        [articleListViewController noMore];
    }
}];

[[ModelManager getInstance] readArticle:article];

〜略〜

補足:
データソースへのアクセスは基本的にコストがかかるので
メインスレッド以外で処理します。
ローカルのsqliteから一件とって返すだけでもボタンのハイライトが効かなくなったりします。

で、なにが便利なの

アプリ自体(Controller, Model)がデータソースの実装に引きずられなくなります。
→ 常に動く成果物

データソースの切り替えが容易になります。
in ModelManager

-(void) initialize{
// NSObject* datasource
    datasource = [DataSourceFactory createDataSource];
//    datasource = [DataSourceFactory createRemoteDataSource];
//    datasource = [DataSourceFactory createDbDataSource];
//    datasource = [DataSourceFactory createDebugDataSource];
}

対象のモデルに応じてデータソースを切り替えるのもありでしょう。
とりあえずDebug使ってて、webApiの用意ができたところからRemoteを使う、とか。

-(void) initialize{
 debugDataSource = [DataSourceFactory createDebugDataSource];
 remoteDataSource = [DataSourceFactory createRemoteDataSource];
}
// webapi実装済み
-(void) reloadArticleList:(ModelManagerCallback_t)callback{
    [SLThreadUtil threadBlock:^{
        [remoteDataSource reloadArticleList:articleList];
        [SLThreadUtil mainBlock:callback];
    }];
}
// もっと読むは未
-(void) moreArticleList:(ModelManagerCallbackMore_t)callback{
    [SLThreadUtil threadBlock:^{
        BOOL hasMore = [debugDataSource moreArticleList:articleList];
        [SLThreadUtil mainBlock:^{
            callback(hasMore);
        }];
    }];
}

まとめ(1年前の自分へ)

XMLを吐き出すWebApi, ローカルに保存してるsqlite, アプリ実行時にとりあえず生成するNSDictionary
いずれもデータソースの一つの実装です。

モデルからみればデータソースの実装はどうでもよくて
各モデルが表す情報を持ってれば良いだけ。

開発の中で難しいのはそのモデルが何なのかっていうのを理解すること。
記事(Article)って何?
タイトルがあって、日付があって、サムネイルがあって、既読情報もってて、etc...

そしてモデルが何かを理解するにはviewに突っ込んで表示してみて、画面遷移させてみることが必要です。
動くものが出来上がる前にすべてを洗い出すことは難しいです。
常に動くものが確認できる環境はけっこう大事。

フレームワーク使ってるとテーブルとオブジェクトを勝手にマッピングしてくれるので
忘れそうになりますが、データベースありきのモデルではなくてモデルの永続データとしてのデータベースです。

参考

0 件のコメント:

コメントを投稿