スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

オブジェクト指向入門 第13回 情報隠蔽

今回は、プログラムを分割してモジュール化する際の方針/基準である、情報隠蔽(Informaton hidding)について書いてみたいと思います。

情報隠蔽はオブジェクト指向に限定されない一般的なモジュール分割基準ですが、情報という言葉が曖昧なためかカプセル化と混同されることもあるようです。

情報隠蔽に関する有名な論文に、D.L.Parnas の "On the Criteria To Be Used in Decomposing Systems into Modules"(1972年)がありますが、そこには何を隠すかについて、

"We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others."

と書かれています。

つまり、そのモジュールに関する決定事項を他のモジュールから隠すということです。


以前に作成したパックマンのプログラムを、再び例にしたいと思います。

あの時は、最初にモンスターをクラスにした際に、赤モンスターを次のように書きました。

        class RedMonster : IMonster
        {
            void IMonster.Move()
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("追いかけます。");
                Console.ResetColor();
            }
        }

しかし、これは他の色のモンスタークラスとコードが重複する個所が多かったので、それを無くすために基本クラスを追加して、モンスタークラス間で重複するコードを基本クラスに移しました。

その結果、赤モンスタークラスは、以下のようになりました。

        class RedMonster : Monster
        {
            protected override ConsoleColor GetSkinColor()
            {
                return ConsoleColor.Red;
            }

            protected override string GetAlgorithm()
            {
                return "追いかけます。";
            }
        }

この「クラス間で重複するコードを基本クラスに移す」が、決定になります。
これは難しい決定ではないですが、これにより赤モンスタークラスは丸ごと書き換わっています。
変更前と変更後を行単位で比較しますと、中カッコ以外の行は全て変更があります。

モジュールレベルでは大変更となる決定をしたわけですが、モンスタークラスを使用する呼び出し側のモジュールは一行も変更がありませんでした。

これが、決定事項を他のモジュールから隠すということです。
開発者レベルでは、クラスを変更した際は、その事を呼び出し側モジュールの担当者へ連絡するでしょう。
作業レベルでも、変更箇所の動作確認を呼び出し側モジュールを結合した状態で行うことは、普通にあるでしょう。
しかしモジュールレベルでは、この決定が完全に隠されていて、変更が変更を呼ぶといった変更の連鎖が発生していないのです。


アルゴリズム変更の決定なども、情報隠蔽の対象となります。例えば、売上履歴を検索する検索モジュールと、検索結果を並び替えるソートモジュールがあるとします。
この時、ソートモジュールのパフォーマンスが向上するようにアルゴリズムを変更したとしても、検索モジュールに変更が発生しないようにソートモジュールの引数などを設計します。
また、検索精度が向上するように検索モジュールのアルゴリズムを変更したとしても、ソートモジュールに変更が発生しないように検索結果のデータ構造を設計をします。


しかし、モジュール分割はコーティング前にする作業ですが、変更の決定はコーティング後にも発生する事があります。

このジレンマを解消するのに、オブジェクト指向が役立ちます。

カプセル化やインターフェイスは、クラスの内部構造や実装方法を隠蔽するのに役立ちます。
ポリモーフィズムを上手に使えば、新しいモジュールを追加しても、その決定を隠蔽したまま他のモジュールに追加モジュールを使わせる事ができます。

外部的振る舞いを保ったまま内部構造を改善するリファクタリングは、正に情報隠蔽のテクニック集です。

情報隠蔽を意識しながらクラス設計をすると、オブジェクト指向の様々な知識を効果的に使えるでしょう。


■まとめ
  ・情報隠蔽を意識して、オブジェクト指向を使いこなす。

■ご参考
  ・デイビッド・パーナス
   "On the Criteria To Be Used in Decomposing Systems into Modules"


次回に続きます。
スポンサーサイト

テーマ : ソフトウェア開発
ジャンル : コンピュータ

オブジェクト指向入門 第12回 長~い補足

前回は、オブジェクト指向言語に合わないクラス構成を、パワー型に着目して改善する例を書きました。

今回は、クラス構成、パワー型、言語の3つについて、補足したいと思います。


さて、ラモスの例では、生成後に国籍や職業を変えたいため、クラス構成を見直す必要がありました。しかし、それらを変える必要がなければ、次のような構成も考えられます。

ramos_ng.jpg

しかし、このように組み合わせの数だけクラスを作る構成はオススメしません。
拡張するのが、面倒だからです。

試しに、イギリス人と監督の2要素を追加してみます。

ramos_ng2.jpg

5クラスも追加されました。
次に、新たな要素として、性別(男性/女性)を加えてみます。

ramos_ng3.jpg

省略していますが、12クラスも追加しています。

組み合わせを使った構成では、追加したい要素数と追加するクラス数が一致しなくなります。そして、拡張すればするほど、1要素追加に必要なクラス数が増えていくのです。

前回のクラス構成なら、イギリス人と監督で1クラスずつと、性別/男性/女性の3クラス、先ほどの12クラスに対して5クラスで済みます。常に要件の数だけの追加、つまり一定量の追加で済むのです。

ramos5.jpg

ファウラーの著書リファクタリングの「継承の分割」の章で、もっと詳しく扱っています。
押さえておくと、非常に役立つと思います。


次は、パワー型についてです。
インスタンスが、オブジェクトではなくクラスという、風変わりな型ですが、意外と役立ちます。

パワー型を基本クラスにすると、継承関係を小さく最適化できるからです。
ラモスの例の様な大雑把な継承は、オブジェクトが何クラスに属するかを変えたくなる事態を招きがちです。
これを避けることができます。

また、インスタンスがクラスというのは、身近な考え方でもあります。
典型例は、会社の役職です。
役職のインスタンス(実例)をあげると、部長/課長/主任などがあります。
これらは、それぞれが人を権限などの特性ごとに分類するための集合、つまりクラスです。

なお、このようなクラスには抽象クラスが便利です。

以前のパックマンのプログラムでは、モンスタークラスを抽象クラスで実装しました。なぜなら、モンスターの実例も、赤モンスターや青モンスターなど、自身の派生クラスだからです。

インスタンスがクラスの場合、オブジェクトを生成しないので、抽象クラスがちょうど良いのです。


最後に、オブジェクト指向言語に合わない例を、もう一つ書いてみます。
派生クラスにも似たような事があります。

当たり前ですが、派生クラスが欲しい時は派生クラスのコードを書きます。

この書き足す行為自体も、どうしようもない事ですが、言語向きの派生クラスと、そうではない派生クラスを生み出してしまいます。


言語向きの派生クラスは、基本クラスに機能を付け足す派生クラスです。
自動車にパトライトとサイレンを付けてパトカーにする様に、基本クラスに機能を追加する派生クラスは、言語仕様に非常にマッチします。

逆に、基本クラスの機能を制限するような派生クラスは、慎重な設計を求められがちです。

例えば、箱クラスの派生クラスとして、硬貨しか入らない貯金箱クラスを作る等です。

その時、とりあえず箱クラスに何でも放り込める操作を作り、貯金箱クラスでそれをオーバーライドして、硬貨以外はエラーにする様にしたとしましょう。

この設計はシンプルですが、少々厄介な面があります。

引数で指定した箱オブジェクトに、色々なものを入れる処理を作ったとしましょう。

箱オブジェクトを指定していれば全く問題ありませんが、ポリモーフィズムを利用すると、引数で貯金箱オブジェクトを指定する事もできます。

そうすると、貯金箱に色々入れようとしてエラーしてしまうのです。


貯金箱クラスの設計者は、エラー処理を考慮していない事や、そもそも貯金箱に硬貨以外を入れようとする事自体を非難するかもしれません。
しかし、箱オブジェクトを使用する側からすると、派生クラスを基本クラスとして使えない事、つまりポリモーフィックでは無い事を問題視するでしょう。

いずれにせよ、エラーする時点で、設計が要件や仕様を満たしていないのは明らかです。それを適切に満たす設計になるように、お互いに歩みよる必要があります。

まず、要件や仕様を満たし、その後でポリモーフィックかどうかも確認すれば、トラブルにならないでしょう。


実際のプログラム開発でも、「この時だけ特別に、〇〇処理をしないで!」といった要望は多々あります。
しかし、派生クラスで機能を削ろうとするのは、ポリモーフィズムが破綻しやすいので、避けたほうが無難でしょう。

機能を削りたくなるというのは、基本クラスが大きくなりすぎている事が表面化したと言えます。

拡張性の確保や条件分岐の排除など、ポリモーフィズムの利点だけを継承するように、基本クラスをスリムにするのが良いと思います。


■まとめ
  ・継承は、小さく最適化する。


貯金箱の例のような状況は、曖昧な理由で統一感を求めると起きやすい気がします。
例えば、箱に物を入れる方法を何となく統一するなどです。

具体的なやり方を無理に統一するのは、ジャンケンでいつもグーを出すような危険性があります。
プログラム開発で無理にやり方を統一すると、その方法が有効な処理だけ良く動作し、それ以外はバグに悩まされるプログラムになりかねません。

ジャンケンは、相手の裏をかくという方針でグー/チョキ/パーを使い分けます。開発も、方針を統一して、方法は使い分けると安全です。


次回は、方針について書いてみたいと思います。

テーマ : ソフトウェア開発
ジャンル : コンピュータ

オブジェクト指向入門 第11回 オブジェクトを生成する時は、クラスを一つ指定する

今回はラモスの例の続きです。

前回、オブジェクトの特徴として、それがどの集合(クラス)に属するかは生成した時に決まり、以降は変えられない事について書きました。

オブジェクト指向言語のオブジェクトには、もっと基本的な特徴があります。
それは、

  ・生成する時、どのクラスに属するかを指定しなければならない。
  ・指定できるクラスは、一つだけである。

というものです。
当たり前過ぎる事ですが、このルールに反するクラス設計はさけなければなりません。


もう一度、ラモスの例を見てみましょう。

ramos.jpg

ご存知の通り、ラモスはブラジルから日本に帰化してますし、以前はサッカー選手でしたが最近は監督をしています。

オブジェクトを生成する時に一つのクラスしか指定できない事を考えますと、多彩な人生を過ごしているラモスを生成するのに適したクラスは、上図には無いと言えます。


前回の問題も今回の問題も、同じ方法で解決できます。

再度、クラス図を確認してください。

派生クラスが三つありますが、一つだけ仲間外れがあります。
サッカー選手クラスです。

他の派生クラスは国籍で特化していますが、このクラスだけは職業で特化しています。

このように継承する理由が派生クラスごとに違いますと、今回のように生成時にクラスを一つ指定できず困ってしまいます。

なぜなら、理由ごとに一つずつクラスを指定したくなるからです。
このようなケースの多は、ブラジル人オブジェクトが欲しいわけでも、サッカー選手オブジェクトが欲しいわけでもなく、ブラジル人サッカー選手オブジェクトが欲しいのです。

C++では、多重継承を使ってブラジル人サッカー選手クラスと一つにできましたが、様々な理由で敬遠され、JavaやC#では作れなくなりました。

そこで継承する理由は必ず一つにします。


まず、クラス図に理由を書き足して見ます。

ramos2.jpg

継承が二つの固まり(セット)に別れました。
このセットをGeneralizationSetと言います。

そして、個々のセットに付けた名前(国籍と職業)はクラスの一種で、パワー型と言います。


次に、パワー型のインスタンスを考えます。つまり、実例をあげます。
国籍ならブラジル国籍や日本国籍などが実例と言えます。

不思議な事に、パワー型のインスタンスもクラスであり、パワー型をクラスにすると自然にインスタンスを自身の派生クラスとできます。

ramos3.jpg

最後に、今までの派生クラスの代わりに、これらを属性にします。

ramos4.jpg

これで、解決しました。
Personクラスに属するオブジェクトは、属性のオブジェクトを変える事で、国籍も職業も自由に変えられます。
多重度を増やせば、多重国籍を取得したり、二足のわらじを履かせたりもできます。


■まとめ
  ・生成する時、どのクラスに属するかを指定しなければならない。
  ・指定できるクラスは、一つだけである。


この話は、もう少しだけ続きます。

テーマ : ソフトウェア開発
ジャンル : コンピュータ

オブジェクト指向入門 第10回 オブジェクトは何に属するかを変えられない

今回は、以前に書いたラモスの例を使って、オブジェクト指向言語では一工夫しないと表現出来ないモデルについて書いてみたいと思います。

あの時は、オブジェクトとインスタンスの違いを表現するために、ラモスという一つのオブジェクトが様々なインスタンスになる様子を、次のように書きました。

    // 生成直後にブラジル人のインスタンスとなる
    Brazilian RuyGoncalvesRamosSobrinho = new Person();

    // ラモスという名前のサッカー選手のインスタンスとなる
    FootballPlayer Ramos = RuyGoncalvesRamosSobrinho;

    // ラモス瑠偉という名前の日本人インスタンスとなり、ブラジル人のインスタンスではなくなる
    Japanese RamosRuy = RuyGoncalvesRamosSobrinho;
    RuyGoncalvesRamosSobrinho = null;

    // 引退したので、サッカー選手のインスタンスでもなくなる
    Ramos = null;

しかし、このソースはJavaやC#ではコンパイルが通りません。
一行目からコンパイルエラーになってしまいます。

この現象を理解するために、まずは例に出てくるクラス間の関係を図にしてみます。素直に描くと、クラス図は次のようになると思います。

ramos.jpg

この図とコードを対比すると、一行目で何が起こっているのか見えてきます。

    Brazilian RuyGoncalvesRamosSobrinho = new Person();

まず、右辺では基本クラスPersonクラスのオブジェクトを生成しています。
そして左辺に代入して、生成したオブジェクトをBrazilianのインスタンスとして扱おうとしています。
ベン図にすると次のようになります。

Venn.jpg

この図から、オブジェクトがベン図上を移動していることが判ります。
つまり、一行目が成立するには、ある集合(クラス)に属するオブジェクトが、別の集合(クラス)に属する存在に変わる必要があるのです。

この変わる事が、オブジェクト指向言語にできないことです。
ニ行目以降も同様です。オブジェクトがどのクラスに属するかは生成した時に決まり、途中で変える事はできないのです。


一方、派生クラスのオブジェクトを、基本クラスのインスタンス用変数へ代入する事は可能です。

    Member member = new PremiumMember();

ポリモーフィックな処理でよく見る書き方です。
これが可能なのは、派生クラスのオブジェクトはベン図上で動かなくても最初から基本クラスに属しているからです。

Venn2.jpg

ポリモーフィズムは、オブジェクトがどのクラスに属するかを変えない事が前提になっています。
代入しただけで変わると、様々なオブジェクトの振る舞いが全て基本クラスと同じになり、ポリモーフィズムが破綻してしまいます。


しかし、これはプレミアム会員が、普通の会員に戻れない事も意味しています。

オブジェクト指向言語でも、ブラジル人が日本人に帰化したり、普通の会員がプレミアム会員になったり/やめたり、RPGの主人公がクラスチェンジ(転職)する事を、直接的には表現できないのです。

このような時は、少し視点を変える事で回避します。


少し長くなりますので、次回に続きます。


■まとめ
  ・オブジェクトは、どの集合(クラス)に属するかを変えられない。
  ・生成した時に決まる。

テーマ : ソフトウェア開発
ジャンル : コンピュータ

オブジェクト指向入門 第9回 集約/委譲/カプセル化

今回は、集約/委譲/カプセル化についてサクサクと書いてみたいと思います。


まず、集約(Composition)です。
作曲家をComposerと言いますが、構成/合成して1つにする事をCompositionと言います。
UML2.0では「合成」と訳される事が多いです。

例えば、自動車がエンジンやタイヤで構成されていますが、このような時に「自動車はエンジンやタイヤを集約している」と表現します。
扇風機にモーターが内蔵されている構造を、クラス図にしますと次のようになります。

Fan.jpg

このモーターなど、構成要素の事を属性(Property)や部品(Part)と言います。
Partより複数形のPartsの方が、外来語として定着している言い方と思います。

他にも、ユーザー情報がID/パスワード/個人情報で構成されているなど、集約はプログラムのデータ構造を表現する際に非常に多用されます。
その個人情報も名前/住所/電話番号・・・で構成されるなど、階層的に使用する事もよくあります。

データベースと相性が抜群に良い概念でもあります。


次に、委譲(delegation)です。

文字通り、あるオブジェクトの操作の一部を、他のオブジェクトに委譲/委任する事です。

委譲は重複コードを減らす際に役立ちます。
以前のケータイの例を考えて見ましょう。

MobilePhone.jpg

この図では、カメラとケータイがカメラI/Fを実装しています。
しかし、カメラとケータイの両方に写真を撮る機能を書くと、コードが重複してしまいます。

そこでカメラをケータイの部品として、処理を委譲する事で重複を回避します。

    class MobilePhone : Phone, ICamera
    {
        private ICamera camera = new Camera();  // 集約する

        void ICamera.TakePicture()
        {
            camera.TakePicture();   // 処理を委譲する
        }
    }

このように、委譲は集約関係にあるクラス間でよく使用されます。


最後に、カプセル化(Data Encapsulation)です。
Encapsulationは要約という意味の単語で、何かの最も重要な部分を数単語で表現する事です。

オブジェクト指向では、あるクラスが自身の属性や部品へ不正なアクセスをされない様に防ぐ事をいいます。
先ほどのケータイの例では、カメラをprivateで定義する事で、外部から勝手にカメラを制御されない様にしています。

このように、公開するものを最小限に要約する事をカプセル化と言います。


今回の3つはセットで使用される事が多いです。
操作の一部を集約した部品に委譲して、その部品は非公開にし、操作だけを公開する事で要約するといった感じです。


■まとめ
  ・集約・・・データ構造を表現する。
  ・委譲・・・処理の一部を移譲する。
  ・カプセル化・・・インターフェイスを要約する。


さて、今までいろいろな事を書いてきました。
  ・クラス
  ・オブジェクト
  ・インスタンス
  ・継承
  ・実装
  ・ポリモーフィズム
  ・集約
  ・委譲
  ・カプセル化

これらを活用すると、プログラムの要件や仕様を適切に表現できるようになります。
しかし、中には一工夫しないと表現が難しいものもあります。

次回は、そういったオブジェクト指向が苦手にしている事について書いてみる予定です。

テーマ : ソフトウェア開発
ジャンル : コンピュータ

プロフィール

Author:きっぽ

カテゴリ
最新記事
最新コメント
最新トラックバック
月別アーカイブ
FC2カウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。