yumeの日記: Interface、ようやく使えてる感じがしてきた 20
UnityでのC#の話。
Interfaceについて、以前は「そもそも使い道がわからない」ので全然使わなかったが、「とにかくクラスじゃなくてInterfaceを参照する」ということを徹底したら、なんとか使えるようになってきた気がする。
『Factorio』のようなゲームを試作している。大雑把に言えば「資源を入力としてを受け取って、別の資源を出力するプリンターを組み合わせて、高効率を目指す」というやつ。Factorioを抽象化して、そこからまだアイデアがあるんだけどそれは別の話。
ゲームがゲームなので、いろんな機能を持ったり持たなかったりのバリエーションがすごく多い。触れないし壊せないけど、接続はできるし情報も読み取れる。とか。そういうのをまさにInterfaceでITouchableとか定義していく。すごくわかりやすい。
それに、実体ではなくInterfaceを参照することで「相手が何かはしらんけど、触れるなら触る」みたいな抽象的な考え方がそのまま実装できる。「とにかくパイプが接続できるなら、接続する」としておけば相手がプリンターだろうがゴミ排出機だろうが接続できるという具合だ。
Interfaceは何度も書き直した。この辺りはまだチカラがたりてない感じがする。つまり充分なInterfaceをきっちり書くには、そもそも何をInterfaceにするのか、みたいなことがはっきり考えられないといけないんだろう。
--
キャストの話。
IConnectable(接続性)インターフェースを参照しているとき、それがIDemolishable(被破壊性)インターフェースも継承したクラスへの参照なのかどうかはわからないけど、もし被破壊性を持つなら破壊したい……。というようなことをしようとして少し悩んだ。
それがtargetという名前の変数だったとして
target.Connect();
IDemolishable targetDemolishable = target as IDemolishable;
targetDemolishable?.Demolish();
これでいけた。
asでキャストしたとき、キャストできない場合はnullが入る。それをnull条件演算子付きで呼べば「いけたらいく」みたいなことができる。便利である。
オブジェクト指向ですね (スコア:0)
「デザインパターン」ってのを一度調べると良いと思います。
デザインパターンは先人たちが「Interfaceは何度も書き直した。」結果見つけた知恵なので、参考になるはずです。
ただデザインパターンは拘りすぎると不毛な沼にハマってしまいます。例えるとゲーム用のライブラリばっかり何度も書き直して、ゲーム自体を作る時間がなくなるみたいな。とりあえずさっとwebでデザインパターンを検索して、上位のページを流し読みするぐらいがいいかも。
Re: (スコア:0)
『デザインパターン』の第1章を読んで、ふに落ちなかったらオブジェクト思考の
理屈の本を読めばいい。第2章以降はベストプラクティスみたいなものなので、
理屈がわかってなければ理解の足しにはならないと思う。
素人が個人的に感じるのは、『デザインパターン』前後で継承関係が深いものから
浅いものに変わったこと。継承は柔軟性がないのでできるだけ使わないというのが
デザインパターンあたりの考えです。Interface <- Implementの1段がほとんどですね。
これによって理屈どおり、継承 = ISa関係というのが明確・徹底できるように
なったんじゃないでしょうか。
とでも考えておけばOKでは? 継承が浅いので、Interfaceを作るのは必要になってから
リファクタリングでやればいいという考えもあります。たいした手間ではないので。
Re: (スコア:0)
デザインパターンはねえ…
「既存のオブジェクト指向言語が不備な概念を、オブジェクトで無理やり実装している」面がかなり大きいので、まあバッドノウハウなんですわ。典型的なのが多重ディスパッチ。
しかしバッドではあるが洗練されてしまったので、なんかイケてるもののように感じられるのも事実。
Re: (スコア:0)
そうですね。だから2章以降はとりあえず読まなくていいと思う。
しかし継承なんてできるだけ使うなとはっきりいったのは、よいことだったと思う。
個人的には『デザインパターン』で初めて読んだんだけど、どっかでは常識だったんだろうね。
まあ25年位前だから、それはどうでもいいか。
Re: (スコア:0)
まず、デザインパータン≠GoF本です。
デザインパターンは手本にすべき実装のカタログでもないですし、
デザインパターンはオブジェクトと対応させるものでもないです。
Re: (スコア:0)
パターンの発見→抽象化→概念形成、が科学の鉄則です。たとえばコールバックパターン、future、非同期処理などです。概念は一般に非常に広く、非同期処理はランデブーのような他の抽象化も由来に持ちます。コールバックの別の概念化では遅延評価があります。非同期処理と遅延評価は異なる概念です。
メソッドの動的バインドは間接呼び出しのオブジェクト指向における抽象化ですが、多重ディスパッチは抽象化ではなく概念です。なぜならもはやオブジェクト指向は無関係だからです。
GoF本などはおぼろげながらなところはあるが概念にたどり着いていて(それがGoF本の優れた点です)
Re: (スコア:0)
パターンは実装するものではなく、実装するときに意識するものです。
パターンは実装に浮かび上がるものであり、実装を解釈するときに利用するものです。
GoF 本で各パターンの解説に利用されている言語を取り上げてバッドノウハウだとか
無理やり実装されていると表現されているのは違和感があります。
Visitor と多重ディスパッチを 1:1 で対比させるのは不適切です。
多重ディスパッチには Facade や Adaptor などの側面もありますが
State を用いて説明するのがよいと思います。
Multiple dispatch においては Arguments が State です。
Re: (スコア:0)
> > パターンの発見→抽象化→概念形成、が科学の鉄則です。
> パターンは実装に浮かび上がるものであり、
これは「(実装における)パターンの発見」にほかなりません。
> > GoF本以外には「(再利用可能な)パターンの発見止まり」「抽象化止まり」の本もあるのでしょう。
> 実装を解釈するときに利用するものです。
これは「パターンの再利用」にほかなりません
> GoF 本で各パターンの解説に利用されている言語を取り上げてバッドノウハウだとか
> 無理やり実装されていると表現されているのは違和感があります。
それはあなたの認識が世間とずれているから
Re: (スコア:0)
20種類以上あるものをまとめてバッドノウハウ扱いとは恐れ入りますね。
イテレータパターンに至ってはメジャーな言語なら大体言語仕様ないし標準的なライブラリにはいってますが。
GOFのデザインパターンを今プログラマが現代的な言語上で実装するのを車輪の再発明だと言うのは納得できる。
GOFのデザインパターンと言うか設計手法というもの全般は古くなるものなのでそこはしょうがない。
コンピュータサイエンスとか工学が科学かって話もあるのか。
Re: (スコア:0)
> 20種類以上あるものをまとめてバッドノウハウ扱いとは恐れ入りますね
プログラミング言語の進歩は実はめちゃくちゃ遅いのです。
バッドノウハウがグッドアイデアになるには何十年もかかるのが普通です。Simula(これはバッドノウハウではないが)からGoまで洗練されるのでさえ40年かかっています。
メジャーな言語がイテレータを構文でサポートするようになったのも20年より前ではないですよね。十分今風なイテレータ構文は少なくとも1974年のCLUにはありますが。
またバッドノウハウはアドホックだからたくさんあるのは当たり前だし、それなりに洗練されているし、なによりプログラマの共通言語になっているのでグッドアイデアが出たあとでも生き続けます。
みんなはたぶんバッドノウハウの「バッド」に気を取られすぎです。当時はバッドでなくても今はバッドなものもあります。nullポインタとか番兵法とかです。
Re: (スコア:0)
Simulaからgoに至るまでに気付いた偉大な発見て継承はないほうがいいってことでしょ。
Re: (スコア:0)
「呼び出される関数を決定する」とは「ある関数を選択する」という振舞いの集合であり
振舞いを選択する際に参照される「関数名以外の複数の要素」Arguments があり
そして Arguments とは視点を変えれば振舞いそのものを表すものであって
Arguments を内部状態としてみるならば多重ディスパッチの Arguments は
内部状態によって振舞い「ある関数を選択する」を変化させる State として捉えることができます。
GoF 本はパターンに名前を付けただけではなく関連するものにも名付けを行っています。
規範的な実装が与えられたのではなく、規範的な名前が与えられたのです。
「バッドノウハウ」という語の使用から
概念を直接コードレベルに落とし込むことに囚われているような印象を受けます。
Re: (スコア:0)
> 「呼び出される関数を決定する」とは「ある関数を選択する」という振舞いの集合であり
> 振舞いを選択する際に参照される「関数名以外の複数の要素」Arguments があり
ここまが概念です。で、ここまでだけを切り出したやつが偉いのです。なぜならあなたのようにそれができない人が大半だからです。
> 規範的な実装が与えられたのではなく
さすがにGoF本に乗っているコードを読んでこれを言える人は少数派でしょう。それとも「与えられた『だけ』でなく」の間違いですか?
Re: (スコア:0)
規範的と実装の意味わかってます?
Re: (スコア:0)
> 「呼び出される関数を決定する」とは「ある関数を選択する」という振舞いの集合であり
> 振舞いを選択する際に参照される「関数名以外の複数の要素」Arguments があり
ここまが概念です。で、ここまでだけを切り出したやつが偉いのです。
Multiple dispatch は状態を持たないというあなたの主張に対する
Multiple dispatch にも State を見いだせるという指摘なのに
Multiple dispatch の概念の説明を繰り返されたり、概念の切り出しについて解説されても困ります。
切り出した人が偉いことには強く同意しま
Re: (スコア:0)
> Multiple dispatch にも State を見いだせる
それをしては「いけない」という話です。
人間は一般に「関係がない」「いけない」を考えるのは非常に不得手です。
このことだけはあらゆる人に肝に銘じてほしいです。
議論が誰かの役に立てばと思いましたが、あなたとはこれ以上は無駄に思いました。おつき合いいただいてありがとうございました。
あなたをバカにするつもりはありません。わたしがかつて通った道です。
Re: (スコア:0)
> Multiple dispatch にも State を見いだせる
それをしては「いけない」という話です。
パターンの中にパターンが現れ
パターンの Participant は別のパターンの Participant として解釈できる、
ただそれだけです。
見出されたパターンをすべて見たまま実装しないといけないということではありません。
何かの一部分を理解する時に特定のパターンを利用するだけです。
特定の文脈で特定のパターンを使用してはいけないというのは初耳ですし、
非常に不自由だと思います。
インターフェースは概念であって、性質ではありません (スコア:0)
たとえば数には「破壊する」という概念を当てはめること自体が不可能なので、NumberクラスにはIDemolishableインターフェースは実装しないでしょう。
「破壊不可能なターゲット」は「破壊しようとすることはできるが、実際は破壊できない」という性質だから、破壊の概念があります。だからIDemolishableインターフェースにDemolish()メソッドを定義し、TargetクラスがIDemolishableを実装し、必要ならneverDemolishedプロパティを設けます。
破壊不可能なターゲット(Targetのサブクラス)では、Demolish()メソッドは空にし、neverDemolished=trueと定数を設定するといいでしょう。
そうるすると
target.Demolish();
だけで済みます。必要なら
if (target.neverDemolished) { print("このターゲットは破壊不能です"); }
とも書けます。
「接続」はまた別の概念ではありませんか?
Re: (スコア:0)
「無敵モード」中なら破壊可能なターゲットでもDemolish()が効果を及ばさないと思います。
無敵モードはクラスではなくオブジェクト固有の状態で可変なのが常識的だと思いますが、破壊不能ターゲットの永続的な破壊不可能性はクラスの性質と考えるのもまた自然です。サブクラスはこういうものの表現に適しています。
Re:インターフェースは概念を示すためのものだと思ってた (スコア:0)
インターフェースはAPIの説明を(コメントもドキュメントも使わずに)コードとして行うためのものだと思ってますね。
異なるクラスが同じ関数名でシグネチャも同じでAPI上全く同じみたいな関数をもってるときにそれぞれの関数がどういったものかを示すためのもの。
関数自体のインターフェース(API)だと区別がつかないのでその関数がどのインターフェイスに基づいて実装されたものかをクラスが実装しているインターフェイスを調べて確認すると。
ぱぱっと考えたのだとゲームの場合ゲームのログを管理するクラスとゲーム内の機能としての送信機(狼煙とか手旗とか