型テストはいつ大丈夫ですか?


53

固有の型安全性を備えた言語(たとえばJavaScriptではない)を想定:

を受け入れるメソッドを考えるSuperTypeと、ほとんどの場合、アクションを選択するために型テストを実行するように誘惑される可能性があることがわかります。

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    o.doSomethingA()
  } else {
    o.doSomethingB();
  }
}

通常は、常にではありませんが、単一のオーバーライド可能なメソッドを作成し、SuperTypeこれを行う必要があります。

public void DoSomethingTo(SuperType o) {
  o.doSomething();
}

...ここで、各サブタイプには独自のdoSomething()実装が与えられます。アプリケーションの残りの部分は、与えられたものSuperTypeが実際にaであるSubTypeAかa であるかを適切に無視できますSubTypeB

素晴らしい。

しかし、is aすべてではありませんが、ほとんどのタイプセーフ言語で-のような操作が与えられています。そして、それは明示的な型テストの潜在的な必要性を示唆しています。

だから、何で状況は、もしあれば、必要があります我々たりはしなければならない我々は、明示的な型のテストを実行しますか?

私の不在や創造性の欠如を許してください。私は前にそれをやったことを知っています。しかし、正直なところ、私がやったことが良いかどうか覚えていません!そして最近の記憶では、カウボーイJavaScriptの外部で型をテストする必要に遭遇したとは思いません。



4
JavaもC#も最初のバージョンではジェネリックを持たなかったことを指摘する価値があります。コンテナを使用するには、Objectとの間でキャストする必要がありました。
ドーバル14

6
「型チェック」とは、ほとんどの場合、コードが言語の静的型付け規則に従っているかどうかをチェックすることを指します。あなたが言うことは、通常型テストと呼ばれます


3
これが私がC ++を書いてRTTIをオフにしておくのが好きな理由です。実行時にオブジェクトタイプを文字通りテストできない場合、開発者はここで尋ねられている質問に関して適切なオブジェクト指向設計に従う必要があります。

回答:


48

「タイプテストはいつ大丈夫ですか」に対する正解は「決して」ではありません。これを証明または反証する方法はありません。それは、「優れた設計」または「優れたオブジェクト指向設計」を実現するものに関する信念体系の一部です。また、ホクムです。

確かに、クラスの統合セットと、そのような直接型テストを必要とする1つまたは2つ以上の関数がある場合は、おそらく間違っているでしょう。本当に必要なのは、異なる方法で実装されたメソッド SuperTypeとそのサブタイプです。これはオブジェクト指向プログラミングの一部であり、クラスと継承が存在する理由全体です。

この場合、型テストが本質的に間違っているからではなく、明示的に型テストが間違っているのではなく、言語に既にタイプ差別を達成するためのクリーンで拡張可能な慣用的な方法があり、それを使用しなかったからです。代わりに、原始的で壊れやすい、拡張不可能なイディオムにフォールバックしました。

解決策:イディオムを使用します。提案したように、各クラスにメソッドを追加し、標準の継承アルゴリズムとメソッド選択アルゴリズムにどのケースが当てはまるかを決定させます。または、基本型を変更できない場合は、サブクラスを作成してそこにメソッドを追加します。

これまでの知恵と、いくつかの答えに。明示的な型テストが理にかなっている場合:

  1. 一度限りです。行うべき型差別がたくさんある場合は、型またはサブクラスを拡張できます。しかし、あなたはしません。明示的なテストが必要な場所は1つか2つしかないので、クラス階層に戻って関数としてメソッドを追加するのに時間をかける価値はありません。または、そのような単純で制限された使用法のために、基本クラスの種類の一般性、テスト、設計レビュー、ドキュメント、またはその他の属性を追加する実際的な努力の価値はありません。その場合、直接テストを行う関数を追加するのは合理的です。

  2. クラスを調整することはできません。サブクラス化について考えますが、できません。たとえば、Javaの多くのクラスが指定されていfinalます。aをスローしようとするpublic class ExtendedSubTypeA extends SubTypeA {...} と、コンパイラーは、あなたがしていることは不可能だと不明確な言葉で告げません。申し訳ありませんが、オブジェクト指向モデルの洗練と洗練!誰かがあなたのタイプを拡張できないと決めました!残念ながら、標準ライブラリの多くはfinalであり、クラスの作成finalは一般的な設計ガイダンスです。関数の最後の実行は、利用可能なものです。

    ところで、これは静的に型付けされた言語に限定されません。動的言語Pythonには、Cで実装されたカバーの下では実際に変更できない多くの基本クラスがあります。Javaのように、ほとんどの標準型が含まれます。

  3. あなたのコードは外部です。さまざまなデータベースサーバー、ミドルウェアエンジン、および制御または調整できない他のコードベースからのクラスとオブジェクトを使用して開発しています。あなたのコードは、他の場所で生成されたオブジェクトの消費者です。サブクラスができたとしても、サブクラスSuperTypeでオブジェクトを生成するために依存するライブラリを取得することはできません。彼らは、あなたのバリアントではなく、彼らが知っている型のインスタンスをあなたに渡します。これは常にそうであるとは限りません...時には柔軟性のために構築され、それらはあなたがそれらを供給するクラスのインスタンスを動的にインスタンス化します。または、ファクトリで構築するサブクラスを登録するメカニズムを提供します。XMLパーサーは、このようなエントリポイントの提供に特に優れているようです。例参照 またはPythonのlxml。しかし、ほとんどのコードベースは そのような拡張機能を提供していません。彼らはあなたが彼らと一緒に構築され、知っているクラスをあなたに返そうとしています。通常、純粋にオブジェクト指向のタイプセレクタを使用できるように、結果をカスタム結果にプロキシすることは意味がありません。型差別を行う場合は、比較的大雑把に行う必要があります。型テストのコードは非常に適切に見えます。

  4. 貧しい人々のジェネリック/複数ディスパッチ。コードにさまざまなタイプを受け入れ、非常にタイプ固有のメソッドの配列を持っていることは優雅ではないと感じます。 public void add(Object x)論理的なようだが、ではないの配列は addByteaddShortaddIntaddLongaddFloataddDoubleaddBooleanaddChar、およびaddString変異体は、(少数を示すため)。高度なスーパータイプを取得し、タイプごとに何をすべきかを決定する機能または方法を持っている-彼らは毎年恒例のBooch-Liskov Design SymposiumでPurity Awardを受賞するつもりはないが、ハンガリー語の命名 は、よりシンプルなAPIを提供します。ある意味では、あなたis-aまたはis-instance-of テストとは、ネイティブサポートされていない言語コンテキストで汎用またはマルチディスパッチをシミュレートすることです。

    ジェネリックカモの両方のタイピングの組み込み言語サポート により、「優雅で適切なことをする」可能性が高くなるため、タイプチェックの必要性が減ります多重ディスパッチジュリア・アンドゴーなどの言語で見られる/インタフェースの選択は、同様にして直下型のテストを置き換えるビルトインのタイプベースの選択のための仕組み「何をすべきか。」しかし、すべての言語がこれらをサポートしているわけではありません。Javaの例は一般にシングルディスパッチであり、そのイディオムはアヒルのタイピングにはあまり適していません。

    しかし、これらすべての型判別機能(継承、ジェネリック、ダックタイピング、および複数ディスパッチ)を使用しても、オブジェクトの型に基づいて何かを実行しているという事実を作成する単一の統合されたルーチンがあると便利な場合があります明確かつ迅速。でメタプログラミング 私はそれは、本質的に避けられない発見しました。直接型問い合わせにフォールバックするかどうかは、「実用的なプラグマティズム」であるか「ダーティコーディング」であるかは、設計哲学と信念に依存します。


特定の型で操作を実行できないが、暗黙的に変換可能な2つの異なる型で操作を実行できる場合、両方の変換で同じ結果が得られる場合にのみオーバーロードが適切です。そうしないと、.NETのようなぎこちない動作になります(1.0).Equals(1.0f)。true[引数はに昇格しますdouble]が(1.0f).Equals(1.0)、false [引数はobject]に昇格します。Javaでは、Math.round(123456789*1.0)123456789を生成しますが、Math.round(123456789*1.0)123456792を生成します[引数は、floatではなくに昇格しますdouble]。
supercat 14

これは、自動型キャスト/エスカレーションに対する古典的な議論です。少なくともエッジケースでは、悪い逆説的な結果。私は同意しますが、あなたがそれを私の答えにどのように関連させるつもりなのかわかりません。
ジョナサンユーニス14

私はあなたのポイント4に答えていましたが、それは異なるタイプの異なる名前のメソッドを使用するのではなく、オーバーロードを提唱しているようです。
supercat 14

2
@supercat私の視力が悪いと思うが、2つの式はMath.round私と同じように見える。違いは何ですか?
リリーチョン14

2
@IstvanChung:メールで送信...後者はされている必要がありますMath.round(123456789)[誰かが書き換えられた場合に何が起こるかを示すMath.round(thing.getPosition() * COUNTS_PER_MIL)ことを実現していない、スケーリングされていない位置の値を返すためにgetPosition戻っAN intlong。]
supercat

25

私がこれまで必要としていた主な状況は、equals(other)メソッドのような2つのオブジェクトを比較するときでした。メソッドでは、の正確なタイプに応じて異なるアルゴリズムが必要になる場合がありotherます。それでも、それはかなりまれです。

私がそれを見つけた他の状況は、非常にまれですが、逆シリアル化または解析後です。より特定の型に安全にキャストするために時々必要です。

また、制御できないサードパーティのコードを回避するためにハックが必要な場合もあります。定期的に使用したくないものの1つですが、本当に必要なときにそこにあることを嬉しく思います。


1
デシリアライゼーションのケースを使用していたことを誤って覚えていたので、私は一時的に喜んでいた。しかし、今は自分がどうなるか想像できません!私はそのために奇妙な型検索をいくつか行ったことを知っています。しかし、それが型テストを構成するかどうかはわかりません。たぶん、シリアル化は、より密接な並行処理である可能性があります。具体的なタイプのオブジェクトを調べる必要があります。
svidgen 14

1
通常、シリアル化はポリモーフィズムを使用して実行できます。シリアル化を解除する場合BaseClass base = deserialize(input)、型がまだわからないため、多くの場合、のような操作を行います。その後、型をif (base instanceof Derived) derived = (Derived)base正確な派生型として保存します。
カールビーレフェルト14

1
平等は理にかなっています。私の経験では、このようなメソッドはしばしば「これらの2つのオブジェクトが同じ具象型を持っている場合、すべてのフィールドが等しいかどうかを返します。それ以外の場合は、false(または比較不能)を返します。」
ジョンパーディ14

2
特に、型をテストしていることに気付いたという事実は、多相性の等価比較が毒蛇の巣であることを示す良い指標です。
スティーブジェソップ14

12

標準(ただし、まれに)のケースは次のようになります。次の状況の場合

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    DoSomethingA((SubTypeA) o )
  } else {
    DoSomethingB((SubTypeB) o );
  }
}

機能DoSomethingAまたははDoSomethingB容易の継承ツリーのメンバー関数として実装することができませんSuperType/ SubTypeA/ SubTypeB。たとえば、

  • サブタイプは、変更できないライブラリの一部であるか、または
  • DoSomethingXXXそのライブラリにコードを追加すると、禁止された依存関係が導入される場合。

多くの場合、この問題を回避できる状況があります(たとえば、SubTypeAおよびのラッパーまたはアダプターを作成するか、の基本的な操作の観点から完全にSubTypeB再実装をDoSomething試みるSuperTypeことによって)が、これらのソリューションは面倒な作業や価値がない場合があります明示的な型テストを行うよりも複雑で拡張性の低いもの。

昨日の仕事の例:オブジェクトのリストの処理を並列化する状況がありました(SuperTypeタイプが2つだけの異なるサブタイプで、これ以上存在する可能性が非常に低い場合)。並列化されていないバージョンには2つのループが含まれていました。1つはサブタイプAのオブジェクトを呼び出しDoSomethingA、もう1つはサブタイプBのオブジェクトを呼び出しDoSomethingBます。

「DoSomethingA」および「DoSomethingB」メソッドは、どちらもサブタイプAおよびBのスコープで使用できないコンテキスト情報を使用した時間集約的な計算です(したがって、サブタイプのメンバー関数として実装することは意味がありません)。新しい「並列ループ」の観点からは、それらを一様に処理することで物事がはるかに簡単になるのでDoSomethingTo、上と同様の機能を実装しました。ただし、「DoSomethingA」と「DoSomethingB」の実装を調べると、内部での動作が大きく異なることがわかります。したがってSuperType、多くの抽象メソッドを使用して拡張することで汎用の「DoSomething」を実装しようとしても、実際には機能しません。


2
私の霧の脳にこのシナリオは不自然ではないことを納得させるために、小さな具体的な例を追加できますか?
svidgen 14

明確にするために、私はそれ不自然だと言っているわけではありません。私は今日最高の霧脳だと思う。
svidgen

@svidgen:これは不自然なことではありません。実際、今日私はこれらの状況に遭遇しました(ビジネスの内部が含まれているため、この例をここに投稿することはできません)。そして、「is」演算子の使用は例外であり、まれにしか行われないという他の回答にも同意します。
ドックブラウン14

私が見落としていたあなたの編集が良い例だと思います。これは、あなたがしてもOKであることを特徴とする請求...そこインスタンスで行う制御SuperTypeおよびそれのサブクラスを?
svidgen 14

6
具体例を次に示します。WebサービスからのJSON応答のトップレベルコンテナーは、辞書または配列のいずれかです。通常、JSONを実際のオブジェクトに変換するツール(NSJSONSerializationObj-Cなど)がありますが、応答に期待するタイプが含まれていることを単純に信頼したくないので、使用する前にチェックします(例if ([theResponse isKindOfClass:[NSArray class]])...) 。
カレブ14

5

ボブおじさんがそれを呼ぶように:

When your compiler forgets about the type.

彼のClean Coderエピソードの1つで、Employees を返すために使用される関数呼び出しの例を挙げました。ManagerはのサブタイプですEmployeeManagerのid を受け入れて彼をオフィスに呼び出すアプリケーションサービスがあるとします:)関数getEmployeeById()はsuper-typeをEmployee返しますが、このユースケースでマネージャーが返されるかどうかを確認します。

例えば:

var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
   throw new Exception("Invalid Id specified.");
manager.summon();

ここでは、クエリによって返された従業員が実際にマネージャーであるかどうかを確認しています(つまり、マネージャーであると期待し、そうでなければすぐに失敗します)。

最良の例ではありませんが、結局それは叔父のボブです。

更新

記憶から思い出せる範囲で例を更新しました。


1
この例でManagerの実装がsummon()例外をスローしないのはなぜですか?
svidgen 14

@svidgenはs CEOを呼び出すことができますManager
user253751 14

@svidgen、その場合、employeeRepository.getEmployeeById(empId)がマネージャーを返すことが期待されることはそれほど明確ではありません
イアン14

@イアン私はそれを問題として見ていません。呼び出し元のコードがを要求しているEmployee場合、それは、Employee。の異なるサブクラスにEmployee異なる許可、責任などがある場合、実際の許可システムよりもタイプテストが優れたオプションになるのはなぜですか?
svidgen

3

型チェックはいつOKですか?

決して。

  1. 他の関数でその型に関連付けられた動作を持つことにより、Open Closed Principleに違反しています。これは、is句を変更するか、または(一部の言語では、またはシナリオ)isチェックを行う関数の内部を変更せずに型を拡張することはできないためです。
  2. さらに重要なことは、is小切手は、あなたがリスコフの代替原則に違反しているという強い兆候です。動作するものSuperTypeすべて、どのサブタイプが存在するかについて完全に無知でなければなりません。
  3. いくつかの動作を型の名前に暗黙的に関連付けています。これらの暗黙のコントラクトはコード全体に広がっており、実際のクラスメンバーのように普遍的かつ一貫して実施されることが保証されていないため、これによりコードの維持が難しくなります。

とは言っても、isチェックは他の方法よりも悪くなることはありません。すべての一般的な機能を基本クラスに入れるのは手間がかかり、しばしば問題が悪化します。インスタンスがどの「タイプ」であるかを示すフラグまたは列挙型を持つ単一のクラスを使用することは、すべてのコンシューマーに型システムの迂回を広めているため、恐ろしいことよりも悪いです。

要するに、型チェックは常に強力なコード臭であると考えるべきです。しかし、すべてのガイドラインと同様に、どのガイドライン違反が最も不快でないかを選択せざるを得ない場合があります。


3
1つの小さな警告があります。代数データ型をサポートしない言語で実装することです。ただし、継承と型チェックの使用は、純粋にハックな実装の詳細です。その目的は、サブタイプを導入することではなく、値を分類することです。私は、ADTが有用であり、決して非常に強力な修飾子ではないという理由だけでそれを取り上げますが、そうでない場合は完全に同意します。instanceof実装の詳細を漏らし、抽象化を壊します。
ドーバル14

17
「決して」は、そのような文脈では、特にあなたが以下に書いているものと矛盾する場合、私が本当に好きではない言葉です。
ドックブラウン14

10
「決して」というのが実際に「時々」を意味する場合、その通りです。
カレブ14

2
OKは許容、許容などを意味しますが、必ずしも理想的または最適ではありません。not OK対照的です。何かがOKない場合は、まったく実行しないでください。あなたが示すように、何かのタイプを確認する必要があることはコードのより深い問題を示している可能性がありますが、それが最も便利で、最も悪いオプションである場合があり、そのような状況では明らかにあなたのツールを使用しても大丈夫です廃棄。(すべての状況でそれらを簡単に回避できる場合、そもそも彼らはおそらくそこにいないでしょう。)質問はそれらの状況を特定することになり、助けにはなりません。
カレブ14

2
@supercat:メソッドは、その要件を満たす、できるだけ具体性の低いタイプを要求する必要がありますIEnumerable<T>「最後の」要素が存在するとは約束しません。メソッドにそのような要素が必要な場合、要素の存在を保証する型が必要です。そして、そのタイプのサブタイプは、「最後の」メソッドの効率的な実装を提供できます。
cHao 14

2

大きなコードベース(10万行を超えるコード)があり、出荷に近い場合、または後でマージする必要があるブランチで作業している場合は、多くの対処方法を変更するのに多くのコスト/リスクがあります。

次に、システムの大きな屈折器、またはいくつかの単純なローカライズされた「型テスト」のオプションがあります。これにより、できるだけ早く返済すべき技術的負債が発生しますが、多くの場合そうではありません。

(例として使用するのに十分小さいコードは、より良いデザインをはっきりと見えるようにするのに十分小さいため、例を考え出すことは不可能です。)

または、言い換えれば、デザインのクリーンさに対して「賛成票」を得るのではなく、賃金を支払うことが目的である場合です。


もう1つの一般的なケースはUIコードです。たとえば、ある種の従業員に対して異なるUIを表示しますが、UIの概念をすべての「ドメイン」クラスに逃れたくない場合は明らかです。

「タイプテスト」を使用して、表示するUIのバージョンを決定したり、「ドメインクラス」から「UIクラス」に変換する高度なルックアップテーブルを作成したりできます。ルックアップテーブルは、「型テスト」を1か所に隠すための単なる方法です。

(データベース更新コードにはUIコードと同じ問題が発生する可能性がありますが、データベース更新コードは1セットのみである傾向がありますが、表示されるオブジェクトのタイプに適応する必要があるさまざまな画面が多数存在する可能性があります。)


訪問者パターンは、多くの場合、UIケースを解決するための良い方法です。
イアンゴールドビー

@IanGoldbyは、それが可能な場合があることに同意しましたが、あなたはまだ「型テスト」を行っており、少し隠されています。
イアン14

通常の仮想メソッドを呼び出したときに隠されるのと同じ意味で隠されていますか?それとも別の意味ですか?私が使用した訪問者パターンには、タイプに依存する条件ステートメントがありません。それはすべて言語によって行われます。
イアンゴールドビー14

@IanGoldby、私はWPFまたはWinFormsコードを理解しにくくすることができるという意味で隠されていることを意味しました。一部のWebベースUIでは、非常にうまく機能すると期待しています。
イアン14

2

LINQの実装では、可能なパフォーマンスの最適化のために多くの型チェックを使用し、次にIEnumerableのフォールバックを使用します。

最も明らかな例は、おそらくElementAtメソッド(.NET 4.5ソースのほんの一部)です:

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) { 
    IList<TSource> list = source as IList<TSource>;

    if (list != null) return list[index];
    // ... and then an enumerator is created and MoveNext is called index times

ただし、Enumerableクラスには、同様のパターンが使用される場所がたくさんあります。

したがって、一般的に使用されるサブタイプのパフォーマンスを最適化することは、おそらく有効な使用法です。これがどのように改善されたのかはわかりません。


インターフェースがデフォルトの実装を提供できる便利な手段を提供し、次にのIEnumerable<T>ような多くのメソッドList<T>と、Featuresどのメソッドがうまく機能するか、遅いか、まったく機能しないかを示すプロパティを含めることにより、より良く設計できたかもしれません。消費者がコレクションについて安全に作成できるさまざまな仮定(たとえば、サイズや既存の コンテンツが変更されないことを保証する[ Add既存のコンテンツが不変であることを保証しながら型がサポートする可能性がある])。
supercat

型が異なる2つの完全に異なるものの1つを異なる時間に保持する必要があり、要件が明らかに相互に排他的である(したがって、別個のフィールドを使用することは冗長である)シナリオを除き、一般的にトライキャスティングの必要性は兆候であると考えます基本インターフェースの一部であるはずのメンバーはそうではありませんでした。つまり、一部のメンバーを含める必要があるインターフェイスを使用するコードは、ベースインターフェイスの省略を回避するためにトライキャストを使用するべきではないということではありませんが、ベースインターフェイスを記述するものは、クライアントがトライキャストする必要性を最小限に抑える必要があります
supercat 14年

1

ゲーム開発、特に衝突検出でよく出てくる例がありますが、これは何らかの形式のタイプテストを使用しないと対処が困難です。

すべてのゲームオブジェクトが共通の基本クラスから派生すると仮定しますGameObject。各オブジェクトは、剛体衝突形状有しCollisionShape(ETC、クエリ位置を言って、方位)共通インタフェースを提供することができるが、実際の衝突形状は、すべてのような具象サブクラスになりSphereBoxConvexHull幾何学的オブジェクトの種類に情報の特定を記憶する、等(実際の例はこちらをご覧ください)

さて、衝突をテストするために、衝突形状タイプのペアごとに関数を作成する必要があります。

detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...

これらの2つの幾何学的タイプの交差を実行するために必要な特定の数学を含む。

ゲームループの「ティック」ごとに、オブジェクトのペアで衝突をチェックする必要があります。しかし、私はGameObjectsとそれに対応するCollisionShapes にしかアクセスできません。明らかに、どの衝突検出関数を呼び出すかを知るために、具体的な型を知る必要があります。ここでも、ダブルディスパッチ(これは論理的には型のチェックと違いはありません)で解決することはできません*。

この状況での実際には、私が見た物理エンジン(BulletとHavok)は、何らかの形式の型テストに依存しています。

私はこれが必ずしも良い解決策であると言っているのではなく、この問題に対する可能な少数の可能な解決策の中で最良であるかもしれないというだけです

*技術的に、N(N + 1)/ 2の組み合わせを必要とする恐ろしく複雑な方法でダブルディスパッチを使用することできます(Nは使用しているシェイプタイプの数です)。 2つの形状のタイプを同時に見つけているので、これは現実的な解決策ではないと思います。


1

特定のタスクを実行するのは実際にはその責任ではないため、すべてのクラスに共通のメソッドを追加したくない場合があります。

たとえば、いくつかのエンティティを描画したいが、それらに直接描画コードを追加したくない場合(これは理にかなっています)。複数のディスパッチをサポートしていない言語では、次のコードになる可能性があります。

void DrawEntity(Entity entity) {
    if (entity instanceof Circle) {
        DrawCircle((Circle) entity));
    else if (entity instanceof Rectangle) {
        DrawRectangle((Rectangle) entity));
    } ...
}

これは、このコードが複数の場所に表示され、新しいエンティティタイプを追加するときにどこでも変更する必要がある場合に問題になります。その場合は、Visitorパターンを使用することで回避できますが、場合によっては単純に保ち、過剰に設計しない方が良い場合があります。これらは、型テストがOKな状況です。


0

私が使用するのは、リフレクションとの組み合わせのみです。しかし、その後もダイナミックチェックは、主に、特定のクラスにハードコーディングされていない(またはのみなどの特殊クラスにハードコーディングされましたStringList)。

動的チェックとは:

boolean checkType(Type type, Object object) {
    if (object.isOfType(type)) {

    }
}

ハードコードされていない

boolean checkIsManaer(Object object) {
    if (object instanceof Manager) {

    }
}

0

型テストと型キャストは、非常に密接に関連する2つの概念です。だから、密接に私はあなたがすべきことを言うに自信を感じていることに関連決して種類のテストを行わない場合を除き、あなたの意図は、その結果に基づいてオブジェクトをキャスト入力することです。

理想的なオブジェクト指向の設計を考えるとき、型のテスト(およびキャスト)決して起こらないはずです。しかし、うまくいけば、オブジェクト指向プログラミングは理想的ではないことがわかったはずです。場合によっては、特に低レベルのコードでは、コードが理想に忠実でなくなることがあります。これは、JavaのArrayListsの場合です。実行時に配列に格納されているクラスがわからないため、配列を作成Object[]し、正しい型に静的にキャストします。

型テスト(および型キャスト)の一般的な必要性はEqualsメソッドに由来することが指摘されています。このメソッドはほとんどの言語でplainを使用することになっていますObject。実装には、2つのオブジェクトが同じ型であるかどうかを確認するための詳細なチェックが必要です。これには、それらの型をテストできる必要があります。

また、型テストは頻繁に反映されます。多くの場合、返されるメソッドObject[]またはその他の汎用配列がありFoo、何らかの理由ですべてのオブジェクトを引き出したい場合があります。これは型テストとキャストの完全に合法的な使用です。

一般的には、型式試験は、悪いとき、それは不カップル、特定の実装が書かれた方法にあなたのコード。これにより、線、長方形、円の交点を見つけたい場合など、各タイプまたはタイプの組み合わせに対して特定のテストが必要になる可能性があり、交差関数には組み合わせごとに異なるアルゴリズムがあります。1つの種類のオブジェクトに固有の詳細をそのオブジェクトと同じ場所に配置することが目標です。コードを維持および拡張しやすくなるからです。


1
ArrayListsJavaにはジェネリックがなく、最終的にOracleが導入されたときに、ジェネリックなしのコードとの下位互換性を選択したため、実行時にクラスが格納されることを知りません。equals同じ問題があり、それはとにかく疑わしい設計決定です。等値比較は、すべてのタイプに対して意味をなさない。
ドーバル14

1
技術的には、Javaコレクションはコンテンツを何にもキャストしませ。例えば:各アクセスの場所でコンパイラ注入型キャストString x = (String) myListOfStrings.get(0)

前回Javaソース(1.6または1.5であった可能性があります)を調べたときに、ArrayListソースに明示的なキャストがありました。正当な理由で(抑制された)コンパイラ警告を生成しますが、とにかく許可されます。ジェネリックの実装方法が理由で、Objectとにかくアクセスされるまでは常にそうだったと言えるでしょう。Javaのジェネリックは、コンパイラ規則によって安全にされる暗黙のキャストのみを提供します。
meustrus

0

2つのタイプが関係する決定を行う必要があり、この決定がそのタイプ階層の外部のオブジェクトにカプセル化されている場合は許容できます。たとえば、処理を待機しているオブジェクトのリストで、次に処理するオブジェクトをスケジュールしているとします。

abstract class Vehicle
{
    abstract void Process();
}

class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }

ここで、ビジネスロジックが文字通り「すべての車がボートやトラックよりも優先される」としましょう。Priorityクラスにプロパティを追加すると、このビジネスロジックをきれいに表現できなくなります。

abstract class Vehicle
{
    abstract void Process();
    abstract int Priority { get }
}

class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }

問題は、優先順位を理解するために、すべてのサブクラスを見る必要がある、つまりサブクラスに結合を追加したということです。

もちろん、優先度を定数にし、それらを単独でクラスに入れる必要があります。これにより、ビジネスロジックのスケジューリングを維持できます。

static class Priorities
{
    public const int CAR_PRIORITY = 1;
    public const int BOAT_PRIORITY = 2;
    public const int TRUCK_PRIORITY = 2;
}

ただし、実際には、スケジューリングアルゴリズムは将来変更される可能性があり、最終的には単なるタイプ以上のものに依存する可能性があります。たとえば、「重量が5000 kgを超えるトラックは、他のすべての車両よりも特別な優先順位が付けられます」と言う場合があります。そのため、スケジューリングアルゴリズムは独自のクラスに属しているため、型を調べて、どちらを最初に実行すべきかを判断することをお勧めします。

class VehicleScheduler : IScheduleVehicles
{
    public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
    {
        if(vehicle1 is Car) return vehicle1;
        if(vehicle2 is Car) return vehicle2;
        return vehicle1;
    }
}

これはビジネスロジックを実装する最も簡単な方法であり、将来の変更に対しても最も柔軟です。


私はあなたの特定の例には同意しませんが、異なる意味を持つ異なるタイプを保持するプライベートリファレンスを持つという原則には同意します。より良い例として私が提案するのは、、nulla String、またはaを保持できるフィールドString[]です。オブジェクトの99%が正確に1つの文字列を必要とする場合、すべての文字列を個別に構築されたものにカプセル化String[]すると、かなりのストレージオーバーヘッドが追加される可能性があります。への直接参照を使用して単一文字列の場合を処理するにStringは、より多くのコードが必要になりますが、ストレージを節約し、処理を高速化できます。
supercat

0

型テストはツールであり、賢く使用してください。強力な味方になる可能性があります。不十分に使用すると、コードの臭いがし始めます。

私たちのソフトウェアでは、リクエストに応じてネットワーク経由でメッセージを受信しました。デシリアライズされたすべてのメッセージは、共通の基本クラスを共有しましたMessage

クラス自体は非常にシンプルで、型指定されたC#プロパティとしてのペイロードと、それらをマーシャリングおよびアンマーシャリングするためのルーチンだけでした(実際、メッセージフォーマットのXML記述からt4テンプレートを使用して、ほとんどのクラスを生成しました)

コードは次のようになります。

Message response = await PerformSomeRequest(requestParameter);

// Server (out of our control) would send one message as response, but 
// the actual message type is not known ahead of time (it depended on 
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{ 
    // Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
    // Extract results
}
else if (response is AnotherThingHappenedMessage)
{
    // Extract another type of result
}
// Half a dozen other possible checks for messages

確かに、メッセージアーキテクチャをより適切に設計できると主張することもできますが、それはC#用ではなく、かなり前に設計されたものです。ここで、型テストは、私たちにとって本当の問題をあまりにもみすぼらしい方法で解決しました。

注目に値するのは、C#7.0がパターンマッチングを取得していることです(多くの点でステロイドの型テストです)。


0

一般的なJSONパーサーを使用します。成功した解析の結果は、配列、辞書、文字列、数値、ブール値、またはnull値です。それらのどれでも構いません。また、配列の要素またはディクショナリの値は、これらの型のいずれかになります。データはプログラムの外部から提供されるため、結果を受け入れる必要があります(つまり、クラッシュすることなく受け入れる必要があります。予期しない結果を拒否できます)。


まあ、一部のJSONデシリアライザーは、構造に「推論フィールド」の型情報がある場合、オブジェクトツリーのインスタンス化を試みます。しかし、ええ。これは、カールBの答えが向かっていた方向だと思います。
svidgen16年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.