2015年6月15日月曜日

ファイルの読み書きも間接参照で(追加修正編)

ファイルの読み書きを間接参照で作る話は一旦終わったのですが、使う際の状況をよく考えたら、リンククラスに親クラスが必要だと気付きました。その辺の話を追加で書きます。

 

本題に入る前に、私の普段の作り方を少しだけ。

似たようなクラスを複数作る場合、共通部分を親クラスとして最初に作り、異なる部分はサブクラスとして追加するでしょう。私も同じように作りますが、最初から親クラスを用意することはほとんどありません。似たクラスを作るかどうか不確かなので、似たクラスを作る段階になってから、親クラスを作るのが基本です。

このようにする理由は、無駄な作業を少しでも減らしたいからです。共通部分を親クラスに持つといっても、どの辺までが共通なのかは、似たクラスを実際に作る段階で明確になります。ですから、明確になったときに親クラスを作ったほうが、全体としての作業量が最小限で済みます。そもそも、似たクラスを作るかどうかが、不確実ですから。こう考えているため、最初から親クラスを作ることは、めったにありません。

 

で、今回のリンククラスです。いつもと同じように考えて、親クラスを用意しませんでした。

しかし、よくよく考えると、切り替え可能に作ることが大前提のリンククラスです。切り替え時の変更を最小限に済ませるなら、最初から親クラスを用意するのが、もっとも効率的だと思います。もちろん、切り替え用のリンククラスを作るとき、モデルクラスの中まで含めて変更しても構わないのですが、モデルクラスを変更せずにと考えている以上、やっぱり親クラスは必要でしょう。それに、リンククラスの仕様変更は、生じそうもないですし。

 

というわけで、さっそく親リンククラスを作ってしまいました。出来立てです。

まず、親クラスの役割を説明します。親のリンククラスは、リンククラスの共通部分を持っていますから、どのリンククラスにも使える機能を持ちます。そして、モデルクラスのように、サブのリンククラスを切り替えて使う側に利用されます。このような役割なので、プロトコル的なクラスと考えれば理解しやすいでしょう。

切り替えて使われるサブのリンククラスは、親クラスとは異なる機能を持つだけでなく、共通機能の実装という役割も持ちます。これら2つのうち後半の役割は、プロトコルの実装と考えれば理解しやすいでしょう。

親子クラスなのですが、リンククラスは間接参照が目的なので、プロトコル的な形になるのだと思います。でも、親子クラスとして実現するほうが、作りやすくて扱いやすいので選びました。

 

さて、実際のSwiftコードです。データ形式が4つありますから、それぞれに親クラスを作らなければなりません。

まずは、テキストを扱うリンククラスから。次のようなSwiftコードになりました。

// ============================== 文字列をファイルへ読み書き
// 親クラス(切り替えて利用する側で使う)
class ZSLText2Storage {
    // アプリ内のリソースからロード
    func loadDefaultF() -> String? {
        return nil
    }
    // ロード
    func loadF() -> String? {
        return nil
    }
    // 保存
    func saveF(rStrData:String) -> Bool {
        return false
    }
}
// サブクラス(インスタンスを生成する側で使う)
class ZSLText2File: ZSLText2Storage {
    var cDir: ZFDir = .Docs              // 仮の値
    var cFileName: String = "dummy.txt"
    //初期化
    init(_ rDir:ZFDir, _ rFileName:String) {
        cDir = rDir
        cFileName = rFileName
    }
    // アプリ内のリソースから
    override func loadDefaultF() -> String? {
        let iFileNameN: String = cFileName.stringByDeletingPathExtension
        let iFileNameS: String = cFileName.pathExtension
        let iStr: String? = zFReadFileTextResF(iFileNameN, iFileNameS)
        return iStr
    }
    // 指定されたディレクトリから読み込む
    override func loadF() -> String? {
        let iStr: String? = zFReadFileTextF(cDir, cFileName)
        return iStr
    }
    // 指定されたディレクトリへ書き出す
    override func saveF(rStrData:String) -> Bool {
        let iBool: Bool = zFWriteFileTextF(cDir, cFileName, rStrData)
        return iBool
    }
}

親クラスには、共通する3つのメソッドだけで、初期化は含まれていません。リンククラスの初期化は、保存先が何なのかに依存するため、共通化できないからです。

3つの共通メソッドは、基本的に何もしません。ただし、処理が失敗したときの戻り値を返すように作ってあります。上書きされない場合でも、それなりに良い形で正常に動作するようにとの配慮です。「上書きされてません」とエラーメッセージを出すことも可能ですが、それはやりすぎでしょう。

サブクラスは、基本的に何も変えていません。親クラスが持つ3つのメソッドを上書きするので、メソッドの先頭にoverrideを加えただけです。機能的にも同じですし、使い方も前と変わりません。

 

続いて、NSDictionaryを扱うリンククラスです。こちらも親クラスを追加して、次のようなSwiftコードになりました。

// ============================== NSDictionaryをファイルへ読み書き
// 親クラス(切り替えて利用する側で使う)
class ZSLDic2Storage {
    // アプリ内のリソースからロード
    func loadDefaultF() -> NSDictionary? {
        return nil
    }
    // ロード
    func loadF() -> NSDictionary? {
        return nil
    }
    // 保存
    func saveF(rNSDic:NSDictionary) -> Bool {
        return false
    }
}
// サブクラス(インスタンスを生成する側で使う)
class ZSLDic2File: ZSLDic2Storage {
    var cDir: ZFDir = .Docs                // 仮の値
    var cFileName: String = "dummy.plist"
    //初期化
    init(_ rDir:ZFDir, _ rFileName:String) {
        cDir = rDir
        cFileName = rFileName
    }
    // アプリ内のリソースから
    override func loadDefaultF() -> NSDictionary? {
        let iFileNameN: String = cFileName.stringByDeletingPathExtension
        let iFileNameS: String = cFileName.pathExtension
        let iNSDic: NSDictionary? = zFReadFileDicResF(iFileNameN, iFileNameS)
        return iNSDic
    }
    // 指定されたディレクトリから読み込む
    override func loadF() -> NSDictionary? {
        let iNSDic: NSDictionary? = zFReadFileDicF(cDir, cFileName)
        return iNSDic
    }
    // 指定されたディレクトリへ書き出す
    override func saveF(rNSDic:NSDictionary) -> Bool {
        let iBool: Bool = zFWriteFileDicF(cDir, cFileName, rNSDic)
        return iBool
    }
}

変更点は、テキストを扱うリンククラスと同じです。説明は不要でしょう。

 

このように親リンククラスを追加したので、一部の使い方は変更になります。それは、リンククラスを交換して使うモデルクラスです。今まではリンククラスのデータ型としてサブのリンククラスを指定していましたが、その代わりに、親のリンククラスを指定します。たったこれだけです。

モデルクラスが、親のリンククラスを扱う形になるので、すべてのサブのリンククラスを切り替えて使えるようになり、モデルクラスの変更は生じません。リンククラスの仕様が変更されない限り、モデルクラスはそのまま使い続けられます。

モデルクラス以外では、前に説明したとおりの使い方です。中央処理では、サブのリンククラスとしてインスタンスを生成し、モデルクラスに渡します。また、一括バックアップなどの目的で、サブのリンククラスを操作するときも、前と同じように操作できます。つまり、親クラスの追加で変わったのは、モデルクラス内のデータ型の指定だけなのです。

 

当初の予定外に、親のリンククラスを追加することになりました。ちょっと気付くのが遅かったですが、早めに気付いて良かったです。モデルクラスを使う部分は実際に作ってないため、気付くのが遅くなったのでしょう。もし作ってれば簡単に気付いたのですが。まあ、こんなことは、たまにありますね。素直に修正することが大事です。

以上のように、リンク先の切り替え部分も、将来の変更が最小限になりました。これからも、間接参照の考え方を積極的に活用して、いろいろな機能を実現したいと思います。

 

(使用開発ツール:Xcode 6.3.2, SDK iOS 8.3)

0 件のコメント:

コメントを投稿