からくりがてんこ

IT関連情報、プログラミングに関する作業ログや備忘録を記載していきます。

C言語からの本当のJava言語へ (考え方の移行)

デザインパターン紹介の前に少しだけ…。

オブジェクト指向と言っても、どう考えていけばいいか具体的に分からないということがありませんか?
手続き型の考え方からどう移行していけばよいのか。

ここでは私なりのアプローチを紹介致します。
私はこれでオブジェクト指向に少し近づけた気になりました。
少々長くなりますが、お付き合いくださいませ。
(クラス図も登場しますが、イメージ作り程度で大丈夫です。)

すぐにプログラムを書かない

手続き型の考え方は、処理の順番を中心に考えていきますね。

例えば、単純な四則演算を行うCUIプログラムの場合。

<<処理フロー>>

  1. ユーザーからの入力を待つ(数値)
  2. 数字情報を格納 → 入力チェック
  3. ユーザーからの入力を待つ(演算子)
  4. 演算子情報を格納 → 入力チェック
  5. ユーザーからの入力を待つ(数値)
  6. 数字情報を格納 → 入力チェック
  7. 1つ目の数値と2つ目の数値を演算子に従った計算をする
  8. 結果を表示する

このような処理の流れになるかと思います。

あなたなら、どう考えますか?


for文でグルグル回して、その中で入力待ちをして…。



「ちょっと待った!!」


いきなりそう考えちゃった方は既に、手続き型ってます。


確かに、オブジェクト指向で書いたとしても、for文でグルグル…という処理は入ります。
しかし、オブジェクト指向ではその前にやる事があります。

それは「クラス図」の作成です。
実際、作成しなくてもいいのですが、クラス図を作成できる材料をまとめ、揃えておく必要があります。

材料と言われても…とお考えのことでしょう。
では、材料を揃えるとはどうすればよいか。

データ中心生活

今まで、処理の流れを最初に考えていましたが、ここで一旦「データ」を中心に考えてみましょう。

まず、このシステムにどんなデータがありますか。

  • 数値情報が2つ
  • 演算子情報(+-×÷)

ですね。
これらをオブジェクト(クラス)として定義しましょう。

クラスの定義として、数値情報は二つとも同様の情報なので1つで良いですね。

<登場するクラス>

  • 数値情報クラス
  • 演算子情報クラス

まだ空のクラス定義なので、各クラスに数値や演算子となる値を格納する領域を用意する必要がありますね。
その領域に対する設定/取得メソッドも定義しましょう。(要はsetter/getterですね)


ここで処理フローを確認すると、このシステムにはちゃんと「数値」「演算子」が入力されたかチェックをする仕様があります。
入力チェックはメソッドを作成するとして、この入力チェックメソッドはどこに記述すべきでしょうか。

for文グルグルの中に入力チェックを実装していくから…、とか考えていないですか。
それはまだ、処理を追いかけています。

チェックは誰がすると楽か、誰がその機能を持つべきか。どうあるべきかを考えます。


この作業に慣れるまでは「自分でできることは自分でやる」という方針で一度みてみるのがよいです。
「入力チェックは自分でやる!」→「数値情報クラスと演算子情報クラスに実装する」という観点はどうでしょう。


そうするとクラス図はこのようになります。


<<クラス図>>
f:id:ayumu-homes:20140116123249p:plain


よく見ると2つとも同じような作りですね。
設計方針は様々ですが、ここではインターフェースを定義することにしましょう。
このようなクラス設計を考えて見ます。

<<クラス図>>
f:id:ayumu-homes:20140116123340p:plain

ちょっとらしくなってきました。
(UMLの詳しい説明はしていませんが、△に点線はインターフェースの実装しているという意味です。)


「その処理はどうあるべきか、誰がやるべきか」
「まとめられる処理はないか、共通化はできないか」
を考える癖を付けていきましょう。



さらにこの世界では、形なき思わぬモノがオブジェクトとして考えられます。

「7.1つ目の数値と2つ目の数値を演算子に従った計算をする」にあたる処理もオブジェクトとして定義するとどうでしょう。
役割をオブジェクトとして定義するという感覚です。演算オブジェクトとでも言いましょうか。

そこで定義するメンバ変数には、

  • 数値情報オブジェクト×2
  • 演算子オブジェクト

となるのですが、ここは敢えて

  • 入力情報インターフェース×3

としましょう。

実装するメソッドは

  • 入力情報追加メソッド
  • 計算メソッド(結果表示メソッド内で呼ぶということでprivateとして宣言)
  • 計算結果表示メソッド

<<クラス図>>
f:id:ayumu-homes:20140116123410p:plain


UMLの菱形に矢印はメンバ変数として保持するという意味になります。

大体、クラス図の材料が揃った感じがしますね。
そこでいよいよ処理の方にも目を向けていきましょう。

そうすると…、for文グルグル(3回ループ)の中で、ユーザーからの入力待ちがあり、数値情報か演算子情報オブジェクト生成し、データの格納があり、入力チェックがあり、ループが終わったら、計算、結果表示で終了という流れが想像できるかと思います。


処理の流れで赤字の部分は、処理の流れを考えて初めて出てきました。
形なきこの処理も入力情報オブジェクト生成クラスとして定義してみましょう。


下記、入力情報オブジェクト生成クラスのコードの例です。
(適当なコードではありますがイメージ作りのため。。)

<<ソースコードの例>>

// 入力情報オブジェクト生成
public class InstanceMaker {

    // オブジェクト生成メソッド
    // 引数が1,3の時に数値オブジェクト、引数が2の時に演算子オブジェクトを生成
    public static InputInfo create(int index) {

        // 戻り値となる入力情報インターフェース       
        InputInfo obj = null;
        
        switch (index) {
                case 1:
                        // 数値オブジェクトを生成
                        obj = new NumberInfo();			
                        break;
                case 2:
                        // 演算子オブジェクトを生成
                        obj = new OperatorInfo();
                        break;
                case 3:
                        // 数値オブジェクトを生成
                        obj = new NumberInfo();			
                        break;
                default:
                        // error
        }
        return obj;
    }
}

こうすることで、呼出し元(ここではfor文グルグル処理)では、数値情報なのか演算子
なのか意識する必要がなくなり、インターフェースの情報だけあれば処理できることになります。

では、全てのクラスをまとめた状態が下記に当たります。

<<クラス図>>
f:id:ayumu-homes:20140116123431p:plain

なんか、っぽい姿になってきましたね。
メインクラスのソースコード例はこうなります。

<<ソースコードの例>>

public static void main(String[] args) {
    // 演算オブジェクトの生成
    Calculation calObj = new Calculation();

    for (int i=1; i<=3; i++) {
            // ユーザーからの入力情報格納領域
            String inputData = null;
            // ユーザーからの入力を受け付け、inputDataに格納する処理
            // …
            
            // 入力情報オブジェクト生成クラスを用いて入力情報インターフェース実装オブジェクト生成
            InputInfo info = InstanceMaker.create(i);
            
            // ユーザーからの入力情報を格納
            info.setData(inputData);

            // 入力チェック
            if (!info.validate()) {
                    // error発生、終了
                    System.exit(-1);
            }

            // 演算オブジェクトに追加
            calObj.addInputInfo(info);
    }
    // 計算→計算結果を表示
    calObj.display();		
}

各クラスに役割が分担されているので、for文グルグル処理がずいぶんあっさりしましたね。

ここで、再度確認して頂きたいのが、メインクラスのコードの中に数値情報クラス、演算子
情報クラスが登場してきません。
クラス図を見ても、メインクラスは入力情報インターフェースしか見ていないというこ
とが分かります。(線が繋がってません)
実装された中身は意識していないということですね。

これは非常に重要なことです。

もし、数値情報クラスに修正が入ったとしても、メインクラスに修正が入る事はありません。
テスト工数も大幅に軽減できますね。
また、ちゃんとクラス分割(役割分割)されているので、入力チェックに変更が入った場合や、
演算する対象が2つですが、3つになった場合等の修正箇所がクラス図を見るだけで影響範囲まで分かります。



どうでしょうか。
若干稚拙なプログラム例だったり、ざっくりとした説明となってしまいましたが、考え方の移行のヒントになれば幸いです。

最後にほんのちょっとだけ

MVCモデルで構築されたシステムではM(Model)の部分が大きくなることが自然、というのが持論です。
一般的なDaoクラスや、StrutsフレームワークのActionFormなどです。

私としては入力チェック、データの加工、検索結果の整形など、Modelクラスに持たせるのがスッキリします。
そうあるべきだと思うからです。

設計色々、考え方色々、好みも色々(笑)、正しいなんてものはないと思っています。
多くの意見を取り入れ辿り着いたのが、ベストプラクティス。