Scroogeの最近のブログ記事

Scrooge の、その後

| コメント(0)

 以前、買い物の(こんな言葉を使ったことはないが)最適化のためのアプリとして、Scrooge というのを計画していた。これが、なんと最後に書いたのが 2008年5月のこと。1年半も経っているのに気付き、軽い浦島太郎気分を味わっている。

 で、その後どうなったかといえば、実は細々とやっているのだ。というか、やっていたのだ。とはいえ、過去に書いたような設計はほとんど反故にしてしまった。というのも、iPhone 向けの開発を始めて以来、これまでにも増して「コア部分のプラットフォーム非依存」に気持ちが傾いており、そうなると Palm のデータベース構造に丸ごと依存するような設計はできなくなってしまったのだ。

 そんなわけなので、Scrooge のコア部分は丸ごとピュアな C 言語で記述している。すでに UNIX のシェル上では動作するようにはなっている。あとは iPhone OS や Palm OS のインターフェースでくるんでやるだけだ。


 ……というところで現在に至っているのだが、問題はここからだ。無職な上に引越しをしてしまったので、外食を控えて自炊している。しかし、そんな生活をしていると、生鮮食品がいかに値動きの激しいものであるかが身に沁みてわかる。そして自分の手をじっと見つめて思うわけだ。

 『 Scrooge、無理があるんじゃない……? 』

 果たして Scrooge は世に出るだろうか。はてさて。

【Scrooge】クラス 設計 - 6

 随分時間が空いてしまった。

 これまでに、shopDB, itemDB, priceDB といったデータベースを表現・管理するためのクラスを導入してきた。つまり、ShopList, ItemList, PriceList だ。そして、ShopList と ItemList は KeyList クラスを共通の基底クラスとする...ということになっていた。

 これまでにどれくらい言及したかちょっと記憶が曖昧だが、Scrooge は複数のデータのセットを管理できるようにしたいと考えている。つまり、日用的に購入する食料品と、近々購入を予定している Palm デバイスの価格比較は別のデータとして管理したい、といった要求に応えられるようにしたいのだ。それも任意数のセットを作成できるようにしたい。

 この要求と先のクラスがどう絡むべきか...というのは、当然ながらいくつかの選択肢があるだろう。しかし、今回はできるだけダイナミックヒープの使用を控えたいという考えがある。そのため、データベースを抽象化するクラスはフォームクラスのメンバとして直接持たせることを考える。まぁフォームクラスのインスタンス自体がダイナミックヒープ上に置かれるのだから、結局はダイナミックヒープ上なのだが、だとしてもチャンクの個数だけでも減らしたいと思うわけだ。

 そうなると、アプリケーション起動中(すなわちメインフォーム表示中)に別のデータセットに切り換えたければ、ShopList, ItemList, PriceList はそのライフタイム中にデータベースを切り換えられなければならない。まぁそれが答だろう。最近めっきりコードを書けていないので、こんなことしか書けないが、まぁ前進ということにする。

 続きは明日以降。

【Scrooge】クラス 設計 - 5

 今回は、画面右上のポップアップリストから要素を選択した時の動作から PriceList クラスのインタフェイスを考えてみよう。

 PriceList というのは、前回書いた PriceTable とは別のクラスだ。PriceTable は POL::CGrid 派生の画面コントロールクラスだが、PriceList はデータベースを抽象化するもので、KeyList クラスやその派生クラスに近い。4月の末に書いた以下の図における priceDB をクラスとして表現したものである。

2008042801.gif


 で、画面右上のポップアップリストの選択状態を変更した場合に何が起きるだろうか。ここでは、仮に商品ビューを表示している状況だったとしよう。すると、テーブルに表示されているのは shopDB の一覧で、変更直前のポップアップリストの選択内容に従って ShopDB の各レコードから priceDB のレコードへの参照が張られていたはずだ。参照先の priceDB のレコードは店舗ID が参照元 ShopDB レコードの UniqueID で、なおかつ商品ID が選択中の ItemDB のレコードの UniqueID であるようなレコードである。

 ポップアップリストの選択状態が変化することによって、ShopDB から priceDB への参照が変化する。データベースのレコードを走査するコストをできるだけ小さくして情報の更新を行ないたい。すると、PriceList クラスに主導させるのが最善という結論になる。つまり、priceList の全レコードを反復し、ポップアップリストで選択された要素の UniqueID を持つレコード全てに対して対応するテーブル側のリストのレコードの参照を更新するのだ。日本語で書くと自分でもよくわからないが、それは陰郎の説明が悪いせいだろう。

 ひとまず、PriceList クラスのこの部分だけを示す。名前は適当に決めた UpdateMap メソッド。

class PriceList {
public:
    void UpdateMap( KeyList* pKeyList, UInt32 selectionID );
};

 ちなみに、上記のインタフェイスだけでは、与えられた selectionID を店舗ID と商品ID のどちらに突合させれば良いのかわからない。ここから先は、明日以降。

【Scrooge】クラス 設計 - 4

 実は、ちまちまと記録を残しながらの開発に耐え切れなくなりそう。表現は悪いかもしれないが、小さなアプリケーションの開発というのは思春期の恋愛のようなものだ。視野を狭くして思いにまかせ、短期間に突っ走ってしまった方がいいのである。仮に後から後悔するようなことになったとしても(別に妙な思い出があるわけでもないが)。

 一方、Japonica のような巨大なアプリケーションは長期に渡る現実的な計画とその遂行が必要になるから、倦怠期込みの結婚生活のようなものである。こんなことを書くと「陰郎は両方苦手です」と言いたい気分になるが、結婚の経験はまだないのでそれはまぁ置いておこう。何が言いたかったかというと、Scrooge のような小さなアプリケーションでは、とにかく作るだけ作ってから少しずつ連載した方が良かったかもしれないということだ。開発自体を一気に進めると後追いで検討記事を書く気がしなくなるのでゆっくりやってきたのだが、逆に開発自体のやる気が薄れてしまいつつある。難しいものだ。

 本題に入ることにしよう。一応、ポップアップトリガーとポップアップリストのための KeyList クラスのインタフェイスは決まった。次はテーブルのためのインタフェイスである。

 しかし、ここでちょっと POL の話題に触れておかなければならない。Japonica の時もそうだったが、陰郎はテーブルを使用するときは大抵 POL::CGrid クラスを使用する。これはスクロールをサポートするテーブルに必要な処理の大部分をカプセル化してくれるので、非常に使い勝手が良いのだ。POL::CGrid を使用するには、その派生クラスを作成する。ざっくりだが、現状では以下のような PriceTable というのを作っている。

class PriceTable : public POL::CGrid {
public:
    void SetTargetList( KeyList* pKeyList );
    KeyList* GetTargetList( );
protected:
    virtual void DrawItem( Int16 row, Int16 column,
                           const RectangleType& pBounds );
private:
    KeyList* m_pKeyList;
};


 CGrid クラスからオーバーライドする DrawItem メソッドには、描画対象の行番号と列番号、そして描画領域の矩形が渡される。だから、row が 0 の場合には KeyList の DrawListItem メソッドがそのまま使える。以下のように。

void PriceTable::DrawItem( Int16 row, Int16 column,
                           const RectangleType& rcBounds ) {
    switch( column ) {
    case 0:
        m_pKeyList->DrawListItem( RelativeToAbsolute( row ),
                                  &rcBounds );
        break;
    case 1:
        // 
        // 
        // 
    default:
        break;
    }
}


 続きは明日以降。

【Scrooge】クラス 設計 - 3

 前回、リソースのロックという Palm OS の制約に関連して、KeyList が保有する名称はこのクラス自身に描画させる必要がある、ということを書いた。その後リストコントロールの内容描画に関するインタフェイスにあわせた結果、できあがったのは以下のような KeyList クラスだ。

class KeyList {
public:
    virtual UInt16 GetSize( ) const = 0;
    virtual UInt32 GetRecID( UInt16 idx ) const = 0;
    virtual void DrawListItem( Int16 itemNum,
                               const RectangleType* pBounds ) = 0;
};

 リストの内容コールバック関数で描画する場合、インデックスと描画領域の矩形が渡されるため、それをそのまま KeyList 派生クラスオブジェクト DrawListItem メソッドに渡すわけだ。これで一件落着...と、思ったのだが。

 実装してみると、当然ながらポップアップトリガーに CtlSetLabel せねばならない。この API によって設定される文字列のポインタはユーザーコード側で有効性を保証しなければならないから、レコード上のアドレスを渡した後に平気な顔でアンロックするわけにもいかない...というわけで、一応名称を取得するメソッドを別途用意することにした。もちろん乱用注意で。目的は明確なので、POL の CString を返すようにしてしまおう。

class KeyList {
public:
    virtual UInt16 GetSize( ) const = 0;
    virtual UInt32 GetRecID( UInt16 idx ) const = 0;
    virtual CString GetName( UInt16 idx ) const = 0;
    virtual void DrawListItem( Int16 itemNum,
                               const RectangleType* pBounds ) = 0;
};

 こういう現実的な理由だけでもインタフェイスというのは乱雑になっていくものだ...続きは明日以降。

【Scrooge】クラス 設計 - 2

 前回、MainForm が ShopList と ItemList の2つを所有し、それぞれを配列に格納して入れ替えつつ使用するというところまで話を進めた。今回は、これら2つのクラスに求められる特性について検討してみよう。

 2つのクラスに共通して求められる特性というのは、すなわち共通基底クラスである KeyList のことだ。このクラスの名前についてはもう少しなんとかならないかと思うが、ひとまず良い名前が思いつくまではこのままにする。これらが ShopDB や ItemDB の抽象化であることは明白なので、それぞれがそれぞれのデータベースのハンドルやオープン/クローズを行うといった話は割愛する。

 さて、まずは画面右上のポップアップリストのデータとして使う場合だ。なによりもまず、要素の数が取得できなければならない。そして、各要素の名称が取得できなければならない。そして、ポップアップリストの選択状態が変更された場合にテーブルの更新を行うため、それぞれのレコードの UniqueID が取得できなければならない。ひとまずポップアップリストについてはこれくらいだろう。

 ...と、「これくらい」で済ませてしまってはならない。なぜなら、以下のようなイメージを持ってしまうとマズいからだ。

class KeyList {
public:
    virtual UInt16 GetSize( ) const = 0;
    virtual UInt32 GetRecordID( UInt16 idx ) const = 0;
    virtual const char* GetRecordName( UInt16 idx ) const = 0;
};

 最後のメンバ関数は、レコードをロックしてから文字列ポインタを取得する(逆に言えば文字列ポインタが不要になり次第レコードロックを解除する)必要があるはずなのに、復帰値として得られた NULL 終端文字列の有効期限は定かでない。一般に、リソースのロックなどといった制約がある環境では、上記のようなアクセサメソッドは使用できないのだ。使用できるのは、データ回収用のバッファを渡してコピーを貰う方法か、あるいはデータの有効期限を当のクラス自身が完全に制御できるようにする方法しかない。

 この手の話は、結局効率と汎用性のせめぎ合いになる。Scrooge では、できるだけヒープの使用や冗長なデータ構造を避けようという(個人的な)方針があるため、ひとまずリストの内容を描画するためのメソッドそのものを KeyList に持たせてみることにしよう。コールバック関数を使用してリストの内容を描画する方式の中で、必要なパラメータを渡して KeyList 自身に描画を行なわせることにする。

 続きは明日。

【Scrooge】クラス 設計 - 1

 昨日、実際にコードを起こし始めてみて、全然うまくいかないことに気付いた。まぁそれも無理はない。データベースに格納するデータの構造はイメージできていても、それがそのままコードに対応するとは限らないからだ。もっと言うのであれば、使う言語との細部のすりあわせをしていないからだ。通常、「書きながら考える」いつものやり方では、細部の噛み合わない部分を埋める作業は少しずつ進行するため、ほとんど気にならない。しかし、今回のようなやり方では、細部を詰める作業がまったくないまま大きな構造が先に決まってしまった。結果として細部の噛み合わなさが(陰郎の中では)非常に目立つような気がして疎ましい。

 愚痴を書いていても始まらないので、大雑把なところからコード片をばら撒いてみよう。まず前提として Scrooge ではモードレスフォームは1つだけで、他のモーダルフォームは全てこのメインフォームから表示されると仮定してよい。なので、商品一覧や店舗一覧を抽象化するクラスはこのモードレスフォームのクラスが所有してしまって良いだろう。

2008050401.gif

 そして、画面上のインタラクションについてこれまで見てきたとおり、商品一覧と店舗一覧は互いに交換可能なものとして扱えることが望ましい。現時点では短絡的に、KeyList というクラスを共通基底クラスとして持たせる。

2008050402.gif

 先の図では MainForm から2つのクラスに引かれた線はコンポジションだった。しかし、これはあくまで MainForm がこれらのクラスを所有し、生殺与奪を握ることしか意味していない。メインフォームでは、常にどちらか一方がポップアップトリガーの一覧に表示され、他方はテーブルに一覧表示される。この切り換えを簡単にするために、2つのオブジェクトを使用するためのポインタ配列を用意する。

class MainForm {
//       :
//       :
private:
    KeyList* m_pLists[2];
};

 m_pLists 配列の要素は m_shopList と m_itemList のアドレスが入る。常に最初の要素がポップアップトリガー用であり、最後の要素がテーブル用である。つまり、ビューが切り換えられる度にこの配列のメンバは交換される。アプリケーション起動時の状態復元を度外視すれば、コンストラクタ/デストラクタで以下のようなアロケートと開放を行なうことになる。

MainForm::MainForm( ) {
    m_pLists[0] = new ShopList( );
    m_pLists[1] = new ItemList( );
}
MainForm::~MainForm( ) {
    delete m_pLists[0];
    delete m_pLists[1];
}

 では、実際のところ ShopList と ItemList というクラスは一体どんなものだろうか。メインフォームは、これらのクラスに何を期待するだろうか。それについては明日。

【Scrooge】UI 設計 - 4

 以前、メインフォームではテーブルを使うことにする、と書いた。それ以前に「テーブルだと不都合がある」ということに気付いたような気がしたのだが、思い出せずに通り過ぎてしまっていた。

 しかし、思い出すことができた。何がマズいかというと、5-Way Rocker での操作性が落ちることになるからだ。Scrooge のメインフォームでは、ビュー間の移動や価格の修正など、テーブル内の要素をタップすることで操作することが多い。しかし、5-Way Rocker でテーブルの要素を行・列まで特定してタップするようなアクションはとれないはずだ。ToDo List のようにインプレイスで編集するわけではないから、無理矢理実装しても直感的に分り難いインターフェースになってしまう。

 この問題に気付いたのは随分前だったのだが、長いこと忘れてしまっていた。これといってアイデアも出ないので、努力目標ということにしておこう。


 ところで、叩き台を作るべくコードを起こし始めて気付いたことだが、Palm OS SDK には UniqueID などという型は typedef ですら存在しない。単に UInt32 のパラメータでユニーク ID を扱う場合に仮引数名を uniqueID と名付ける程度だ。何だか情けない勘違いをしていた。そろそろ陰郎の頭も使いモノにならなくなってきたかもしれない。

【Scrooge】データ構造 - 10

 粛々と前進しよう。データベースヒープ上に存在する PriceDB のレコードは可変長で、最低でも ShopDB, ItemDB それぞれへの参照と価格、割引イベントの種類(なし、週次、月次)を持つ。これを単純に構造体にしてみると以下のような感じだ。

struct PriceInfo {
    UniqueID            m_shopID;
    UniqueID            m_itemID;
    UInt32              m_price;
    DiscountSchedule    m_discountInfo;
};

 DiscountSchedule は前回登場した列挙型だ。

enum DiscountSchedule {
    DISCOUNT_NONE    = 0,
    DISCOUNT_WEEKLY  = 1,
    DISCOUNT_MONTHLY = 2
};

 データベースレコードは(当然ながら)連続する有限のメモリ領域だから、割引イベント設定は(もしあれば)PriceInfo 構造体の直後に配置される。PriceInfo 構造体のメンバとしないのは、割引イベントなしの場合のメモリ消費を最低限にしたいからだ。メンバ m_discountInfo の値によって直後に何がくるかが決まる。つまり、WeeklyDiscountInfo か MonthlyDiscountInfo だ。

 実際に実装する時点では、構造体は全てクラスになる。データベースヒープに直接オブジェクトを保存するこれらのクラスは、全てポリモーフィックなクラスにはしない。

 あとは、データベースレコードのロックとオブジェクトの使用をどう連動させるか。続きは後日。

【Scrooge】データ構造 - 9

 今日はあまり時間がないので、小さなトピックについて。PriceDB の「詳細情報」についてだ。一昨日の図をもう一度見てみよう。

2008042801.gif

 詳細情報とは、割引きイベントがある場合に使用されるものだ。問題は、これが可変長のデータになってしまうということだ。4月に入ったばかりの頃の画面イメージをもう一度。

2008040301.gif

2008040201.gif

 割引イベントの種類は、「なし」か「週単位」か「月単位」のいずれかとなる(そう、年単位はやめようと思っている)。この3択の列挙子は詳細情報の先頭にあるべきだ。しかし、そこから先は状況によりけり、ということになる。

 とりあえず、この3択を表現する列挙子を作ってみよう。そして割引きの種類(円または%か)もついでに。

enum DiscountSchedule {
    DISCOUNT_NONE    = 0,
    DISCOUNT_WEEKLY  = 1,
    DISCOUNT_MONTHLY = 2
};
enum DiscountType {
    DISCOUNT_YEN     = 0,
    DISCOUNT_PERCENT = 1
};


 で、いささか乱暴だが週指定の割引きを以下のようにしておく。

struct WeeklyDiscountInfo {
    UInt16       interval;
    bool         dayofweek[7];
    UInt16       discount;
    DiscountType type;    
};


 月指定は以下のようになる。

struct MonthlyDiscountInfo {
    UInt16       discount;
    DiscountType type;
    char         dateList[1];
};

 構造体の最後に長さ1の char 配列を置くのはよく使われるテクニックで、必要な文字列分を余計にアロケートすることで、構造体の後ろに文字列バッファを置くことができるというものだ。これは Palm OS データベースの場合だって使える。

 ...時間切れ。続きは明日以降。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちScroogeカテゴリに属しているものが含まれています。

前のカテゴリはOreBankです。

次のカテゴリはVBGenericです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

  • archives
Powered by Movable Type 5.04