実装へのインターフェイス参照をキャストすることで、著者は何を意味しますか?


17

私は現在C#をマスターしようとしていますが、Gary McLean HallによるC#を介してAdaptive Codeを読んでいます。

彼はパターンとアンチパターンについて書いています。実装とインターフェースの部分で、彼は次のように書いています。

インターフェイスへのプログラミングの概念に慣れていない開発者は、多くの場合、インターフェイスの背後にあるものを手放すのが困難です。

コンパイル時に、インターフェースのクライアントは、使用しているインターフェースの実装を把握していないはずです。このような知識は、クライアントをインターフェイスの特定の実装に結び付ける誤った仮定につながる可能性があります。

クラスが永続ストレージにレコードを保存する必要がある一般的な例を想像してください。そのために、インターフェイスに正しく委任します。これにより、使用される永続的なストレージメカニズムの詳細が隠されます。ただし、実行時にどのインターフェイスの実装が使用されているかについて仮定するのは正しくありません。たとえば、インターフェイス参照を実装キャストすることは常に悪い考えです。

それは言葉の壁か、私の経験不足かもしれませんが、それが何を意味するのかよくわかりません。ここに私が理解していることがあります:

C#を練習するための自由時間の楽しいプロジェクトがあります。そこでクラスがあります:

public class SomeClass...

このクラスは多くの場所で使用されています。C#を学習しているときに、インターフェイスで抽象化する方がよいことを読んだので、次のようにしました。

public interface ISomeClass <- Here I made a "contract" of all the public methods and properties SomeClass needs to have.

public class SomeClass : ISomeClass <- Same as before. All implementation here.

そこで、いくつかのクラス参照をすべて調べて、それらをISomeClassに置き換えました。

私が書いた建設を除いて:

ISomeClass myClass = new SomeClass();

これが間違っていることを正しく理解していますか?はいの場合、なぜそうですか、代わりに何をすべきですか?


25
あなたの例では、インターフェース型のオブジェクトを実装型にキャストしていません。実装タイプの何かをインターフェイス変数に割り当てていますが、これは完全に適切で正しいものです。
カレス

1
「私が書いたコンストラクターで何を意味しますISomeClass myClass = new SomeClass();か?本当にそれを意味するなら、それはおそらくあなたが望むものではなく、コンストラクターでの再帰です。 ?
エリックエイド

@エリック:はい。建設中。あなたは正しいです。質問を修正します。ありがとう
マーシャル

おもしろい事実:F#は、その点でC#よりも優れたストーリーを持っています-暗黙的なインターフェイス実装を廃止しているため、インターフェイスメソッドを呼び出したいときはいつでも、インターフェイスタイプにアップキャストする必要があります。これにより、コードでインターフェイスをいつどのように使用するかが非常に明確になり、インターフェイスのプログラミングが言語により深く組み込まれます。
scrwtp

3
これは少し話題から外れていますが、著者は、この概念に慣れていない人々が抱えている問題を誤診していると思います。私の意見では、問題は、このコンセプトに慣れていない人が良いインターフェースを作る方法を知らないことです。実際には一般性を提供しない特定のインターフェイスを作成するのは非常に簡単です(これはで起こる可能性がありますISomeClass)が、唯一のオプションに対して有用なコードを書くことが不可能な一般的なインターフェイスを作成するのも簡単ですインターフェイスを再考してコードを書き直すか、ダウンキャストすることです。
デレクエルキンズは、

回答:


37

クラスをインターフェイスに抽象化することは、前述のインターフェイスの他の実装を書くつもりであるか、将来そうする可能性が高い場合にのみ考慮すべきものです。

おそらくSomeClass、これISomeClassは悪い例です。OracleObjectSerializerクラスとIOracleObjectSerializerインターフェースを持つようなものだからです。

より正確な例は次のようなものになるだろうOracleObjectSerializerIObjectSerializer。使用する実装を気にするプログラム内の唯一の場所は、インスタンスの作成時です。これは、ファクトリパターンを使用することでさらに分離される場合があります。

あなたのプログラムのどこでも、それがIObjectSerializerどのように動作するかを気にしないで使うべきです。SQLServerObjectSerializerに加えて実装もあると仮定しましょうOracleObjectSerializer。設定する特別なプロパティを設定する必要があり、そのメソッドがOracleObjectSerializerにのみ存在し、SQLServerObjectSerializerには存在しないとします。

対処方法は2つあります。間違った方法とLiskov置換原理アプローチです。

間違った方法

間違った方法、そしてあなたの本で言及されているまさにそのインスタンスは、のインスタンスを取得してIObjectSerializerキャストし、OracleObjectSerializersetPropertyのみ利用可能なメソッドを呼び出すことですOracleObjectSerializer。インスタンスがであることがわかっている場合でも、OracleObjectSerializerプログラムに実装が何であるかを知りたいという別のポイントを導入しているため、これは悪いことです。その実装が変更され、おそらく複数の実装がある場合は遅かれ早かれ、最良のケースのシナリオになると、これらすべての場所を見つけて適切な調整を行う必要があります。最悪の場合、IObjectSerializerインスタンスをにキャストするOracleObjectSerializerと、実稼働環境でランタイムエラーが発生します。

リスコフ置換原理アプローチ

LiskovはsetProperty、私のOracleObjectSerializer場合のように、実装クラスのようなメソッドを適切に行う必要は決してないと言った。クラスOracleObjectSerializerIObjectSerializerに抽象化する場合、そのクラスを使用するために必要なすべてのメソッドを含める必要があります。できない場合は、抽象化に問題があります(たとえば、DogクラスをIPerson実装として機能させようとします)。

正しいアプローチは、setPropertyメソッドを提供することIObjectSerializerです。同様の方法は、SQLServerObjectSerializer理想的にはこのsetProperty方法で機能します。さらに良いことにEnum、各実装がその列挙を独自のデータベース用語に相当するものに変換することにより、プロパティ名を標準化します。

簡単に言えば、anの使用ISomeClassはその半分にすぎません。作成を担当するメソッドの外部にキャストする必要はありません。これを行うことは、ほぼ間違いなく重大な設計ミスです。


1
あなたがキャストしようとしている場合は、ように私には思えるIObjectSerializerOracleObjectSerializerあなたの将来を含むことができる、このコードを維持することができる他の人と、もっと重要なのは、あなたが、これはそれが何であるかであることを「知っている」ので、あなたは自分に正直でなければなりません(とself)、OracleObjectSerializer作成された場所から使用される場所までずっと使用します。これにより、特定の実装への依存関係を導入していることが非常に公開され、明確になります。そのための作業とさ自体が、何かが間違っているという強力なヒントになります。
KRyan

あなたが実際にいくつかの理由であれば(そして、、特定の実装に依存する必要があり、これは、あなたがやっていることであり、あなたが意図と目的でそれをやっているということをたくさん明確になる。これは、もちろん決して起こらない「はずです」そしてそれが実際に起こっているように見えるかもしれない99%の時間は実際にはそうではなく、あなたは物事を修正する必要がありますが、100%確実なことや物事のあるべき姿はありません。)
KRyan

@KRyan絶対に。抽象化は、必要な場合にのみ使用してください。必要でないときに抽象化を使用することは、コードの理解を少し難しくするだけです。
ニール

29

受け入れられた答えは正解であり、非常に便利ですが、あなたが尋ねたコード行について簡単に簡単に説明したいと思います。

ISomeClass myClass = new SomeClass();

大まかに言って、これはひどいことではありません。可能な限り避けるべきことはこれを行うことです:

void someMethod(ISomeClass interface){
    SomeClass cast = (SomeClass)interface;
}

あなたのコードが外部的にインターフェースを提供されるが、内部的にそれを特定の実装にキャストする場合、「それはその実装のみになるとわかっているからです」。たとえそれが本当だったとしても、インターフェイスを使用して実装にキャストすることで、抽象化を使用するふりをすることができるように、自発的に実際の型の安全性を放棄しています。他の誰かが後でコードを操作し、インターフェイスパラメータを受け入れるメソッドを参照する場合、そのインターフェイスの実装はすべて渡すのに有効なオプションであると想定します。必要なパラメータについて特定の方法があることを忘れた後の行。インターフェースから特定の実装にキャストする必要があると感じた場合は、インターフェース、実装、またはそれらを参照するコードが誤って設計されており、変更する必要があります。たとえば、渡された引数が特定のクラスである場合にのみメソッドが機能する場合、パラメーターはそのクラスのみを受け入れる必要があります。

さて、コンストラクター呼び出しを振り返って

ISomeClass myClass = new SomeClass();

キャストの問題は実際には当てはまりません。これのどれも外部に公開されていないようであるため、それに関連する特定のリスクはありません。基本的に、このコード行自体は、インターフェースが最初から抽象化するように設計された実装の詳細であるため、外部のオブザーバーは、何をするかに関係なく同じように機能することを確認します。ただし、これはインターフェイスの存在から何も得ません。あなたはmyClassタイプを持っているISomeClassが、それは常に特定の実装を割り当てられているので、それがどのような理由を持っていません、SomeClass。コンストラクター呼び出しだけを変更することでコード内の実装を交換できる、または後でその変数を別の実装に再割り当てできるなど、いくつかのマイナーな潜在的な利点がありますが、変数をインターフェイスに入力する必要がある他の場所がない限りこのパターンの実装により、コードはインターフェースがロートによってのみ使用されているように見え、インターフェースの利点を実際に理解しているわけではありません。


1
Apache MathはこれをCartesian3D Sourceの行286で実行しますが、これは本当に面倒です。
J_F_B_M

1
これは、元の質問に対処する実際の正解です。
ベンジャミングリュンバウム

2

悪い例でコードを表示する方が簡単だと思います:

public interface ISomeClass
{
    void DoThing();
}

public class SomeClass : ISomeClass
{
    public void DoThing()
    {
       // Mine for BitCoin
    }

}

public class AnotherClass : ISomeClass
{
    public void DoThing()
    {
        // Mine for oil
    }
    public Decimal Depth;
 }

 void main()
 {
     ISomeClass task = new SomeClass();

     task.DoThing(); //  This is good

     Console.WriteLine("Depth = {0}", ((AnotherClass)task).Depth); <-- The task object will not have this field
 }

問題は、最初にコードを記述するとき、おそらくそのインターフェイスの実装が1つしかないため、キャストが引き続き機能することです。将来的には、別のクラスを実装してから(私の例が示すように)、使用しているオブジェクトに存在しないデータにアクセスしてみてください。


どうしてこんにちは。誰があなたがどれほどハンサムか教えてくれますか?
ニール

2

明確にするために、キャストを定義しましょう。

キャストとは、あるタイプから別のタイプに強制的に変換することです。一般的な例は、浮動小数点数を整数型にキャストすることです。キャスト時に特定の変換を指定できますが、デフォルトでは単にビットを再解釈するだけです。

このMicrosoftドキュメントページからキャストする例を次に示します

// Create a new derived type.  
Giraffe g = new Giraffe();  

// Implicit conversion to base type is safe.  
Animal a = g;  

// Explicit conversion is required to cast back  
// to derived type. Note: This will compile but will  
// throw an exception at run time if the right-side  
// object is not in fact a Giraffe.  
Giraffe g2 = (Giraffe) a;  

あなたは可能性が実装インタフェース、そのインタフェースの特定の実装に同じこととキャスト何かをする、しかし、あなたはいけません、あなたが期待するものとは異なる実装が使用される場合には、エラーや予期しない動作になりますので。


「キャスティングとは、何かをあるタイプから別のタイプに変換することです。」-いいえ。キャストとは、あるタイプから別のタイプに明示的に変換することです。(具体的には、「キャスト」はその変換を指定するために使用される構文の名前です。)暗黙的な変換はキャストではありません。「キャスト時に特定の変換を指定できますが、デフォルトでは単にビットを再解釈するだけです。」-確かにそうではありません。暗黙的および明示的な多くの変換があり、ビットパターンへの大幅な変更が伴います。
hvd

@hvdキャストの明示性について修正しました。デフォルトは単にビットを再解釈することだと言ったとき、私はあなたがあなた自身の型を作るなら、キャストが自動的に定義される場合、別の型にキャストするとビットが再解釈されることを表現しようとしました。上記のAnimal/ Giraffe例ではAnimal a = (Animal)g;、ビットを再解釈すると、Giraffe固有のデータは「このオブジェクトの一部ではない」と解釈されます。
Ryan1729

hvdが言っていることにもかかわらず、人々はしばしば暗黙的な変換に関して「キャスト」という用語を使用します。例https://www.google.com/search?q="implicit+cast"&tbm=bksを参照してください。技術的には、他の人が異なる使い方をするときに混乱しない限り、明示的な変換のために「キャスト」という用語を予約する方が正しいと思います。
ruakh

0

私の5セント:

これらの例はすべてOKですが、実世界の例ではなく、実世界の意図を示していません。

私はC#を知らないので、抽象的な例を示します(JavaとC ++の混合)。それでいいことを願っています。

インターフェイスがあるとしますiList

interface iList<Key,Value>{
   bool add(Key k, Value v);
   bool remove(Element e);
   Value get(Key k);
}

今、多くの実装があると仮定します:

  • DynamicArrayList-フラット配列を使用し、最後に高速に挿入および削除します。
  • LinkedList-二重リンクリストを使用して、先頭と末尾にすばやく挿入します。
  • AVLTreeList-AVL Treeを使用し、高速ですべてを実行しますが、大量のメモリを使用します
  • SkipList-SkipListを使用し、高速ですべてを実行します。AVLTreeより低速ですが、使用するメモリは少なくなります。
  • HashList-HashTableを使用します

多くの異なる実装を考えることができます。

次のコードがあるとします:

uint begin_size = 1000;
iList list = new DynamicArrayList(begin_size);

それは私たちが使いたいという意図をはっきりと示していますiList。確かにDynamicArrayList特定の操作を実行することはできませんが、必要iListです。

次のコードを検討してください。

iList list = factory.getList();

今、私たちは実装が何であるかさえ知りません。この最後の例は、ディスクからファイルをロードし、そのファイルタイプ(gif、jpeg、png、bmp ...)を必要としない場合の画像処理でよく使用されますが、必要なのは画像操作(フリップ、スケール、最後にpngとして保存)。


0

ISomeClassインターフェースと、ISomeClassを実装するように宣言されていることを除いてコードから何も知らないオブジェクトmyObjectがあります。

ISomeClassインターフェースを実装するクラスSomeClassがあります。ISomeClassを実装するように宣言されているか、ISomeClassを実装するために自分で実装したためです。

myClassをSomeClassにキャストすることの何が問題になっていますか?2つのことが間違っています。1つは、myClassがSomeClass(SomeClassのインスタンスまたはSomeClassのサブクラス)に変換できるものであることを本当に知らないため、キャストが失敗する可能性があることです。2つ目は、これを行う必要がないことです。iSomeClassとして宣言されたmyClassを使用し、ISomeClassメソッドを使用する必要があります。

SomeClassオブジェクトを取得するポイントは、インターフェイスメソッドが呼び出されたときです。ある時点で、インターフェイスで宣言されているmyClass.myMethod()を呼び出しますが、これはSomeClassに実装されており、もちろんISomeClassを実装する他の多くのクラスにも実装されています。呼び出しがSomeClass.myMethodコードで終わる場合、selfはSomeClassのインスタンスであることがわかります。その時点で、SomeClassオブジェクトとして使用することは完全に適切であり、実際には正しいです。もちろん、実際にSomeClassではなくOtherClassのインスタンスである場合、SomeClassコードには到達しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.