オブジェクト指向再考(メッセージ4、メソッドと関数3)


今月も月末に向かって地味に忙しくなって更新が遅れてしまいました・・・。

 前回と前々回に分けてダブルディスパッチについて説明しました。前回の繰り返しになりますが、この考えをさらに進めると複数のオブジェクトに対してポリモーフィズムを効かせればどうなるか?という話になります。一般化ですね。もっとも残念ながら、3つ以上のオブジェクトに対してポリモーフィズムを効かせる具体例が思いつきません。まぁ、『3者面談をやった時の先生と生徒と保護者の性格別の対応策』というのも考えられなくはないですが、今まで3つ以上のオブジェクトに対してポリモーフィズムを働かせた実戦経験はありません。ただ、この一般化-マルチディスパッチとかマルチメソッド-というアイデアを頭の隅に置いておくといつか使える日が来るかもしれません。

n体のオブジェクトに対してポリモーフィズムを働かせる。

と考えた場合、『n=0の時はどうなるか?』という考えが湧いてくるでしょう。そうです。n=0の時はメソッドに関係するオブジェクトが無いと考えられます。従来の関数がそれにあたります。
C++は自然に関数が定義できるのですが、JavaやC#は悪名高いpublic staticメソッドを使って関数を定義します。

以上が前回の復習になりますが、さて『関数が必要となる場合』の具体例をあげましょう。こちらもC++のライブラリからになりますが、一般的に以下のようなアルゴリズムの実装はオブジェクトと関係ないということで関数(正確にはテンプレート関数)で実装されています。

・合計値を求める
・ソートを行う
・検索を行う。

JavaやC#のみ知っている方はこの事実に驚かれるかもしれません。が、アルゴリズム+データ構造=プログラムという古典を知っている方なら逆に納得してもらえるかもしれません。つまり、データ構造に対して独立した手続き-アルゴリズムを考えた場合、関数での実装が上手くいくということになります。
それでもまだピンとこない人がいるかもしれません。そういう方はジェネリックプログラミングについて調べてみるのも良いかもしれません。この連載でジェネリックプログラムを扱うのは話が発散するので今は避けますがJavaにしてもC#にしてもジェネリックプログラミングをサポートしていますが、オブジェクト指向プログラミングとは必ずしも相容れるものではありません。

ということで関数(手続き)は、アルゴリズムの実装と相性がいいという話をしました。他には、実はかのstaticおじさんと揶揄されているみながわさんが指摘した、通常の手続きにも関数が使えるという話をします。
さて、みなさんがあるアプリケーションの実装時に、あるURLで指定されたHTMLファイルを取得する必要があった場合、以下の2つのライブラリのうちどれが便利に使えるでしょうか?

関数 gethtml を使う。
URLクラス、URLConnectionクラス、InputStreamクラス、BufferedReaderクラスを使う。

もし、各種クラスを使う方が便利と思うなら、あなたを『オブジェクト指向おじさん』に認定しましょう。
gethtml関数の方が良いのは自明でしょう。もし疑問があるのならコメント欄に質問してください。

さて、次回は変数の共有との関係で関数とメソッドを比べてみます。
2016-04-22 | コメント:0件



オブジェクト指向再考(メッセージ3、メソッドと関数2)

 連載も5回目になりましたが、まだモチベーションが維持できています。前回はダブルディスパッチの説明と共にメソッドと関数について本質的な違いはないという説明をしましたが、もう少し突っ込んで話をします。
もともと
object.method();
という表記は、methodがobjectのクラスに属しているという意味合いが強いかと思います。言葉を変えるとクラスとはデータと、そのデータを扱うコードが合わさったものという発想です。これはこれで一つの考え方で構いませんが、問題は全てをメソッドにするのも間違いだということです。その一例がダブルディスパッチということになります。以下のように加算演算子+を考えた場合、

object1 + object2

+演算子は
 1.object1に所属するのか?
 2.object2に所属するのか?
 3.両方か?
 4.どちらでもないか?等々
1-4の内どう考えるのが自然なのか?という問題に突き当たります。Javaは演算子のオーバーロードを禁止することによりこの問題から背を向けました。つまりこの問題を避けているとも言えます。ちなみにこのような仕様を見たときにJavaは補助輪が付いた自転車のような印象を受けました。C++は『どちらにも属さない』書き方ができます。正確にいうと『object1に属する』の書き方も場合によってはできますが、例えば、

10 + object

のような場合は、『どちらにも属さない』書き方でしか書けません。つまり関数を使ってオーバーロード演算子の定義を行います。C#はある意味『どちらにも属さない書き方』で書きます。つまり、多くのオブジェクト厨が忌嫌うpublic staticメソッドでオーバーロード演算子の定義を行います。

ダブルディスパッチの話を終える前にマルチディスパッチ(マルチメソッド)の話を軽くします。前回、2つのオブジェクトに対してポリモーフィズムを働かせる仕組みをダブルディスパッチと説明しましたが3つ以上のオブジェクトに対してポリモーフィズムを働かせる考え方もちろんあり、マルチディスパッチまたはマルチメソッドと言います。マルチメソッドをいつ使うのか?と言われたらそれはそれで考え込んでしまうのですが、それでも、メソッドが何処かのクラスに属するという考え方に固執するとこのようなパラダイムを受け入れることは難しいでしょう

さて、このようなある種の一般化を行った後は、今度は全くクラスに属さない手続き=関数という存在も自然に受け入れることができるかと思います。つまり、

y = sin(x);

のような式において、文字通りの関数、sinはどのクラスにも属さないと考えた方がよいでしょう。「Mathクラスに入れたらいいだろう」と反論を受けそうですが、実際にsin関数はJava厨が忌嫌うpublic staticで定義されており、C++でも関数として定義されています。先の演算子の例でも出てきましたが、public staticメソッドとは手続型言語での関数定義に相当するものになります。ちなみにかのεπιστημηさんは私との議論で、

Java/C#はどのクラスにも属さないメソッドが作れないため、
当たり障りのないクラス(たとえばMathあるいはModuleC)をでっちあげてそいつに押し込まざるを得ない。
「いかなるクラスにも属さない」という"思い"を表現できません。
僕にはそこが(C++と比べて)不自由に感じます。

というふうに本質的に関数であるものを仮のクラス(Math)に入れることには難色を示されています、これについては私も同感です。

さて、『そんなに関数が重要ならsinとかではなくもっと幅広く使える例を出せ!』と言われそうです。その答えは来週に行うということで。
2016-04-10 | コメント:0件



オブジェクト指向再考(メッセージ2、メソッドと関数1)

 なんやかんやでかろうじて連載も4回目になりまして、モチベーションも維持できてよかったです。語学留学もあと一カ月で、最近、仲良くなった大学生にC++を教えています。といっても最初はポインタを教えていたのですが次にリファレンスを教えることになって、思わず、「混乱の元だからリファレンスは最初は無理して覚えなくてよくてポインタを先に覚えましょう。」と言ってしまった。もっとも「リファレンスの方が簡単だから軽く覚えてガッツリポインタを勉強しよう。」とも言ったが、この連載のターゲットは『オブジェクト指向しか知らない世代』としていますが、JavaやC#しか知らない方たちはポインタの概念が理解できているか怪しい限りです。興味深いのは、私の半径3メートルの範囲、クパチーノ近辺ではC++をマスターすることがステータスになっているようです。

 という訳(?)で今回はメソッドと関数について話します。なぜか知らぬがネットの界隈ではどうも、

メソッド>>>どうしようもない壁>>>関数

という図式が成り立っているようです。でこのどうしようもない壁なんて無いというのが今回のテーマです。もっとも1回で説明するのは難しいので何回かに分けて説明します(しかも飛び飛びになるかも)。

先ず、最初に以下の2つの表現

object.function();

function(object);

ですが、C++でみると生成される機械語コードは同じになります。例外はfunctionが仮想関数(ややこしいので以下、オーバーライドメソッドと呼びます)の場合になります。オーバーライドメソッドを除いて、C++でみると上記の2つのコードは同じということになります。『カプセル化があるだろ』という突っ込みがあるかもしれませんが、それは他のキーワード(friend)を使うことによって同等にできます。
もし、あなたには『どうしようもない壁』が見えるというのならどうぞ具体例とともにコメントをください。

ということで、ADPでは上記の2つのコードは同じように解釈されます。というか上のコードは下のコードとして解釈されます。『同じなら一つの書き方で良いだろ!』とお叱りを受けそうですが、表記上の重要な違いがあります。つまりobject.function()の記述はメソッドチェーンを実現できます。

object.function1().function2().function3();

もしこれを関数の形式で書くと

function3(function2(function1(object)));

となりますが、どちらが良いかは一目瞭然だと思います。ただ、これはあくまでも表記上の話でもしこれがどうしようもない壁というのならオブジェクト指向というのは表記上の問題ということで話が付きますし、そもそもメソッドチェーンの見た目はもはやメッセージパラダイムとは異なるでしょう。

さてfunctionがオーバーライドメソッドの場合の話ですが、簡単にオーバーライドメソッドについて話ますと、呼び出されるメソッドが実行時に決定される点です。C言語の関数は呼び出される実体がコンパイル時に決まりますが、オーバーライドメソッドの場合は実行時にオブジェクトの型によって呼び出される実体が決まります。

object.add(1);

とあった場合、objectがInteger型ならIntegerクラスのaddが呼び出され、objectがFloat型ならFloatクラスのaddが呼び出されるということになります。これはポリモーフィズムと呼ばれるもので、オブジェクト指向病にかかっている人たちはまさにポリモーフィズムが手続き型言語との差別化を図っていると信じています。ポリモーフィズムについては連載の後の方で詳しく説明しますが、関数ポインタを使うとこによりCやアセンブリ言語でも同様の機能を実現できます。こう言うと『ならアセンブリ言語で全部書けや!』と意味不明な切り返しをされるのですが、それに答ますとLinuxの記述では主にCが使われているというのは有名な話ですが、でデバイスドライバの実装等ポリモーフィズムと同様のことが行われています。つまりCでもある程度はオブジェクト指向を実用レベルでシミュレートできるということです。ちなみにADPはC++で記述しておりポリモーフィズムもバリバリ使っていますがパフォーマンス上の理由からCでリライトしようかと思案中です。

さて、話が横道にそれましたので元に戻しますとaddの例ですが、何か変なことに気付かないでしょうか?例えば以下の例はどうでしょうか?

object1.add(object2); // ・・・・・(A)

このaddメソッドですが、object1に対してはポリモーフィズムが効きますがobject2に対してはどうでしょうか?JavaやC++ではobject2にはポリモーフィズムは働きません。まぁ関数の例でもそうでしたが引数にはポリモーフィズムは働かないと考えてもしようがない面もありますが、もし引数にポリモーフィズムが働かないのが当然だというのなら(A)は以下の例と動作が異なることになります。

object2.add(object1); // ・・・・・(B)

加算(add)というのは通常交換法則が成り立ちますのでもし(A)と(B)の動作が異なるということならその実装は不完全としか言いようがないですね。ので通常addがポリモーフィズムであるならそれはobject1,object2双方に対してポリモーフィズムでなければならないことになります。2つのオブジェクトに対してポリモーフィズムを働かせることをダブルディスパッチと呼びます。私はダブルディスパッチについて16年程前に『More Effective C++』で知ったのですが、以前、といっても2,3年程前にJavaが得意で自称オブジェクト指向をマスターしている彼が『オブジェクト指向をマスターしている人は日本にはほとんどいない。俺を除いて』と言っていたので『ダブルディスパッチって知っている?』と聞いたら『なにそれ?』と返されたことがある。ということで知ったかのJava厨の検出にはダブルディスパッチは今のところ効果的かと思われる。ちなみにC#4.0以降の場合、dynamicキーワードを使うことにより(A)の記述でもobject2に対してもポリモーフィズムを働かせることができるのでC#プログラマにダブルディスパッチの技を繰り出すのは返り討ちにあう可能性があるので注意したい。話を戻してC++やJavaの場合はかの有名なデザインパターンの一つビジターパターンがダブルディスパッチの実装方法の一つとなる。
ダブルディスパッチの使いどころですが、二項演算子でポリモーフィズムを使いたいとき(もっともパフォーマンス上の問題があり二項演算子でポリモーフィズムは使わない場合が多い)や2つの物体の衝突を計算する場合(More Effective C++)があるでしょう。ADPではユニフィケーションの実装にビジターパターンによるダブルディスパッチを使用しています(これがしたいが為にC++を使ったがもっと効率的な記述をしたいからCで組みなおそうかと考えている)。

つまりこういうことになる。

object.method()



function(object)

において表現上の違いやプログラミング言語のサポート状況による違いはあるが本質的には両者を同等物とみて構わないということである。つまりどうしようもない壁はないということになる。

長くなったので続きは来週に持ち越します。
2016-04-03 | コメント:0件



オブジェクト指向再考(補足1)

前回の記事ですが、読者の方から洗脳を解くには弱いという指摘を受けたのでちょっと補足を考えてみました。実際にオブジェクト指向に洗脳されている方には何をいっても聞かないのですが、そもそも論として、なんでこんな記事を書くのか?ということを説明しましょう。
一般的な話になりますが、エンジニアの中には原理主義者よろしく自身の考えを曲げない人が確かにいます。その人の世界で頑張ってもらえればよいのですが原理主義者の中には妥協という言葉をしらないのか、他人のコードや考え方に口出しをする方もいらっしゃいます。たとえば『手続型はダメ。オブジェクト指向はよい』とかですね。彼・彼女が完成されたエンジニアなら口出しも立派なアドバイスとして成立するでしょうが、ある意味究極のセオリーだと私が考えるのは『コードに正解はない』ということです。あるとすれば『こちらの方がより適切かもしれない』ということと『動くコードはやっぱり最強』ということです。あるエンジニアに、その昔私が書いたコードをありがたく手術と称してリファクタリングしてもらいましたがテストをすると重要なエラーチェックが抜けていることに気づきました。質問すると「まだ書いていない」という返事をもらいました。つまりこういうことです。エラーチェックのコードは時として見栄えが悪くなります。まぁ通常処理に加えて異常系の処理を加える関係上仕方がないでしょう。そういうコードから異常系の処理を取り払えば確かに見栄えはよくなります。さて、見栄えは良いが重要な処理が抜けているコードと見栄えは悪いがきちんと処理が入っているコード、あなたはどちらを評価するでしょうか?見栄えを取るという人は将来失業しないように気を付けましょう。
オブジェクト指向に心酔するのは構わないのですが、結論から言いますと残念ながらオブジェクト指向技術は思ったほど役には立ちません。詳しくは連載を通して理解してもらえればよいのですが、非常に残念なことにオブジェクト指向に過剰に心酔する人がいるのも事実です。例えば、この記事です。
staticおじさん達に伝えたい、手続き指向とオブジェクト指向の再利用の考え方の違いについて 経験のあるエンジニアならこの記事が眉唾だと理解できるのですが経験の浅いエンジニアなら真に受けてしまうでしょう。少し技術的な点を突っ込んでおきます。記事中
アセンブリやCOBOLのような言語では、オブジェクト指向言語で一般的なカプセル化という考え方がきわめて弱いということがあります。変数は静的なグローバル変数が中心であり、
とありますが、アセンブリ言語でもローカル変数はあります。またいわゆるメンバ変数もシミュレートできます。もし仮にアセンブリでローカル変数がなかったりメンバ変数をシミュレートできないとすればJavaやC++のコードはどうやって実行されると考えているのでしょうか?OSはどんなふうに記述されていると考えているのでしょうか?JavaやC++はコンパイラを通して一旦機械語に変換されます。アセンブリとはその機械語に一対一に対応します。また、そもそも論としてアセンブリとCOBOLを同じテーブルに並べるところにもの凄い違和感を感じます。もしあなたが違和感を感じられないのならオブジェクト指向を勉強する前に他に勉強することがあるということになります。
さてこの方の約3年後の他の記事を見てみましょう。
開発チームにアーキテクトがいないなと感じてしまうような、残念なコードスメルの例 引用しますと、
最後に、最近になって気づいた自分の間違えについて書いておかなくてはなりませんね。以前であればこうした設計上の問題は日本のSI業界の構造が問題なのであるという話をしていたかもしれません(^_^;)が、ここで書いたような話は多少フィクションが入っているとはいえ、実際私がSI業界以外の今の会社で体験したことに基づいていると告白しなくてはなりません。言語の特性から、Javaで開発していると、こういった設計上の問題が起きやすいということがある可能性もありますが、こういった話はSIer以外でも、どこの国の開発チームでもあるのだなということですね。
この中にあるJavaで開発していると、こういった設計上の問題が起きやすいということがある可能性もあります この感覚は実はオブジェクト指向症候群から脱する第一歩になります。多くの生き残っているエンジニアは大なり小なりこういう感覚を端緒とし、次に”Javaはダメだ○○は良い”となり、最後には”オブジェクト指向がダメなんだ”となります。もちろんですが私も20年程前にこういう感覚に襲われたことがあります。

まとめますと、オブジェクト指向症候群にかかると間違った知識をまき散らします。さらに病気から抜け出すと後でそれを修正しなければならないという二度手間が発生するわけです。できれば早く抜け出した方が本人の為でもありますし業界のためでもあります。

と長くなったのと月末でいろいろ忙しいので、今週はこれにて終了です。次回は、メソッドと関数についてもう少し突っ込んで話したいと思います。
2016-03-27 | コメント:0件



オブジェクト指向再考(メッセージ1)

先週に続きましてオブジェクト指向再考ということで特にオブジェクト指向に毒された方を対象に現実的な観点でプログラムできるように、プログラマとして社会復帰できるように、ヒントを出したいと思います(もちろん回復するかどうかは本人次第です)。
今週からはメッセージについて話たいと思います。世の中には、

メソッド(メッセージ)>>どうしようもない壁>>関数

と思っている方がいらっしゃるようですが、今後数回に分けて、この『どうしようもない壁』というのはオブジェクト指向病特有の幻覚であるということを示したいと思います。まぁ、厄介なことに患者さんには明確に壁が見えているのでなんとも言えないのですが、もし少しでも『これは幻覚かも?』と思った方はこの連載が助けになるかもしれません。ちなみに『この壁を感じれないやつはプログラマとして終わっている』とお思いの方はお帰り頂いて構いませんし、反論のコメントを書き込んで頂いても構いません。

前回の最後に示しましたが、オブジェクト指向のキーファクターとしてメッセージがあります。この考え方(パラダイム)はそれはそれで素晴らしいものです。分かり切った例をあげますと、マウスの入力(クリック)がメッセージとしてOSに伝わり、イベントハンドラ(コールバック関数)に制御が引き渡されます。当たり前のようですがこの仕組みはメッセージパラダイムを具現化したものになります。もしメッセージを使わなかったら、例えば定期的にマウスの位置を読み込み、クリックされたどうかを確認することになります。考えただけでも厄介ですよね。GUIプログラムがイベント駆動型と言われる所以ですね。

さて、ここでのキーポイントは、『世の中の事象は全てイベント駆動(メッセージ)で記述するのが良いことか?』ということになります。例えば、以下のプログラムを考えましょう。

ユーザがスタートボタンをクリックしたら画面に"Hello"と表示する。

間違いなく、『ユーザがスタートボタンをクリックしたら』、というところはイベント駆動と相性がいいですね。では次の

『画面に"Hello"と表示する。』・・・・・(A)

というのはどうでしょうか?この部分はイベント駆動またはメッセージで記述できるでしょうか?

よくオブジェクト指向の教科書に出てくるのですが、

『画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。』・・・・・(B)

というのを見受けます。最初に指摘しますと(B)は(A)を劣化させたものだと言えます。
ポイントは(A)は手続き指向で書かれて、(B)はオブジェクト指向(メッセージ指向)で書かれています。そして重要な点ですが、この場合、(A)はプログラムの仕様(本当にやりたいこと)を表していて(B)はプログラムそのそのものの説明を行っている点ということになります。つまり、(B)は以下のようなコードの説明を行っているということになります。

label.setText("Hello");

ここで画面オブジェクトというのがlabelになり、setTextが表示依頼メッセージで"Hello"が表示対象の引数ということになります。
つまり(B)は上記のコードの説明を行っていることになり、(A)は上記のコードの意図を表しています。
もし上記のコードにコメントを付与する場合、(A)、(B)どちらが良いでしょうか?

label.setText("Hello"); // 画面に"Hello"と表示する。・・・・・(A)

label.setText("Hello"); // 画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。・・・・・(B)

(B)の方がよいという考え方の人に質問したいのですが、ラベルオブジェクトに値をセットする方法にはプロパティを使うというものもあります。つまり以下のコードもありえます。

label.Text = "Hello";

この場合、(A)、(B)どちらのコメントの方が適切でしょうか?

label.Text = "Hello"; // 画面に"Hello"と表示する。・・・・・(A)

label.Text = "Hello"; // 画面オブジェクトに、引数に"Hello"を指定して表示依頼のメッセージを送る。・・・・・(B)

それとも以下のようにコメントするのでしょうか?

label.Text = "Hello"; // ラベルプロパティに"Hello"をセットする。・・・・・(C)

もっとも、(C)のような発想もオブジェクト指向から離れたと言えるでしょう。
さて、ある程度経験のあるエンジニアなら大なり小なりこのようなジレンマを抱えたことがあるかと思います。つまりメソッドの中身を書こうとしたときに『どうもオブジェクト指向していないな・・・』と感じることがあったかもしれません。しかしよく考えてみれば分かりますが、メッセージ指向というのはある種の手続きの上っ面(呼び出し方法)についてパラダイムであり中身をどうするかは別問題ということに気付くかと思います。
ここで重要なことは、

メッセージというのはプログラミングパラダイムの一つであり、メッセージで表現した方がよいもの(例えばイベント)もあれば、そうでないもの(手続き指向のもの)があるということで、プログラマは適宜適切なパラダイムを選択してプログラムを行えばよいです。多くのプログラマは、プログラミングパラダイムについて

オブジェクト指向(メッセージ指向)> 手続き指向

と考えているかもしれませんが、実はそれぞれ適材適所があり適宜使用すればよいということになります。つまりマルチパラダイムですね。さてオブジェクト指向言語しか知らない方は実は手続き指向という発想がないかもしれません。何かをステップバイステップで処理をするという発想が手続き指向になります。特段難しいとは思えないですが、もしピンとこない方がいらっしゃいましたらコメントを下さい。
イベント処理を手続き指向で考えるのは不適切ですし、能動的に画面に何か表示したいと思った時にいちいちメッセージ云々を考えるのも不適切ということになります。そう考えると以下の2つの記述方法のどちらかが適切か理解できるかと思います。

z = 3 * x * y + 4 * x + 6 * y + 2;

z = 3.multiply(x).multiply(y).add(4.multiply(x)).add(6.multiply(x)).add(2);

もう迷う必要はなく上の方が適切だと理解できるかと思います。数式も一つのパラダイム(というか表現方法の一種)だと理解すれば無用なパラドックスに悩まされる必要もなくなります。

次回は、メソッドと関数についてもう少し突っ込んで話したいと思います。
2016-03-20 | コメント:6件
Previous Page | Next Page