「インターフェースへのプログラミング」を理解する


29

「実装ではなくインターフェースへのプログラミング」という用語に頻繁に出くわしましたが、それが何を意味するのか理解していると思います。しかし、私はそれが利点であり、可能な実装であることを確実に理解したいと思っています。

「インターフェースへのプログラミング」とは、可能な場合、具体的な実装を参照するのではなく、クラスのより抽象的なレベル(インターフェース、抽象クラス、またはある種のスーパークラス)を参照する必要があることを意味します。

Javaの一般的な例は、次を使用することです。

List myList = new ArrayList();の代わりにArrayList myList = new ArrayList();

これに関して2つの質問があります。

  1. このアプローチの主な利点を確実に理解したいと思います。利点は主に柔軟性だと思います。具体的な実装ではなく、より高レベルの参照としてオブジェクトを宣言すると、開発サイクル全体およびコード全体で柔軟性と保守性が向上します。これは正しいです?柔軟性が主な利点ですか?

  2. 「インターフェースへのプログラミング」の方法は他にありますか?または、「具体的な実装ではなく、インターフェイスとして変数を宣言する」がこの概念の唯一の実装ですか?

私はJavaコンストラクトInterfaceについて話しているのではありません。オブジェクト指向の原則「実装ではなく、インターフェイスへのプログラミング」について話しています。この原則では、世界の「インターフェース」とは、クラスの任意の「スーパータイプ」を指します。インターフェース、抽象クラス、またはより具体的なサブクラスよりも抽象的で具体的ではない単純なスーパークラスです。




回答:


44

「インターフェースへのプログラミング」とは、可能な場合、具体的な実装を参照するのではなく、クラスのより抽象的なレベル(インターフェース、抽象クラス、またはある種のスーパークラス)を参照する必要があることを意味します。

これは正しくありません。または少なくとも、完全に正しいわけではありません。

より重要な点は、プログラム設計の観点から来ています。ここで、「インターフェースへのプログラミング」とは、コードの実行方法ではなく、コードの実行内容に設計を集中させることを意味します。これは、設計を正確さと柔軟性に向けて進める重要な違いです。

主なアイデアは、ソフトウェアよりもドメインの変化がはるかに遅いということです。食料品リストを追跡するソフトウェアがあるとします。80年代には、このソフトウェアはフロッピーディスク上のコマンドラインと一部のフラットファイルに対して機能していました。次に、UIを取得しました。次に、データベースにリストを配置します。その後、クラウドや携帯電話、またはFacebookの統合に移行する可能性があります。

実装(フロッピーディスクとコマンドライン)を中心にコードを設計した場合、変更に対する準備が不十分になります。インターフェイスを中心にコードを設計した場合(食料品リストの操作)、実装は自由に変更できます。


答えてくれてありがとう。あなたが書いたものから判断すると、「インターフェースへのプログラミング」が何を意味し、どのようなメリットがあるのか​​理解できたと思います。しかし、1つの質問があります-この概念の最も一般的な具体例はこれです:オブジェクトへの参照を作成するときは、参照型を作成する代わりに、参照型をこのオブジェクトが実装するインターフェイス型(またはこのオブジェクトが継承するスーパークラス)にしますオブジェクトタイプ。(別名、List myList = new ArrayList()代わりにArrayList myList = new ArrayList()(問)は、次のコメントである。
テルアビブコーン

私の質問は、「インターフェースへのプログラミング」の原則が行われている現実世界のコードの場所の例をもっと教えてもらえますか?最後のコメントで説明した一般的な例以外は?
テルアビブコーン

6
いいえList/ ArrayListは私が話していることではありません。これはEmployee、従業員レコードの保存に使用するリンクテーブルのセットではなく、オブジェクトを提供することに似ています。または、曲を繰り返し処理するためのインターフェイスを提供し、それらの曲がシャッフルされた場合、CD上にある場合、またはインターネットからストリーミングされる場合は気にしません。彼らはただの一連の歌です。
テラスティン

「実装ではなく、インターフェイスへのプログラミング」は、抽象化OOの原則を表現する原則であると言いますか?
テルアビブコーン

1
@AvivCohn良い例としてSpringFrameworkをお勧めします。Springのすべての機能は、インターフェイスとSpringsの主な動作を独自に実装することで、機能が期待どおりに機能し続けるように拡張またはカスタマイズできます。インターフェイスへのプログラミングは、統合フレームワークを設計するための最良の戦略でもあります。繰り返しますが、Springはそのスプリング統合を行います。設計時には、インターフェイスへのプログラミングがUMLで行われます。またはそれは私がやることです。その方法から自分自身を抽象化し、をすべきに焦点を当てます。
ライヴ

18

「インターフェースへのプログラミング」についての私の理解は、質問や他の答えが示唆するものとは異なります。それは、私の理解が正しいとか、他の回答の内容が良いアイデアではなく、単にその用語を聞いたときに私が考えていることではないということではありません。

インターフェイスへのプログラミングとは、何らかのインターフェイス(クラスライブラリ、関数セット、ネットワークプロトコルなど)が表示されたときに、インターフェイスで保証されているもののみを使用し続けることを意味します。基礎となる実装についての知識はあるかもしれませんが(書いているかもしれません)、その知識を使うべきではありません。

たとえば、APIが内部値に対する「ハンドル」である不透明な値を提供するとします。あなたの知識は、このハンドルが本当にポインターであるとあなたに伝えるかもしれません、そしてあなたはそれを間接参照して、あなたがしたいいくつかのタスクを簡単に達成することを可能にするいくつかの値にアクセスできます。しかし、インターフェースはそのオプションを提供しません。それは特定の実装に関する知識です。

これに伴う問題は、コードと実装の間に強力なカップリングを作成することであり、まさにインターフェイスが防止するはずでした。政治によっては、コードが壊れる可能性があるため、実装を変更できなくなるか、コードが非常に脆弱であり、基礎となる実装のアップグレードまたは変更のたびに壊れ続けることを意味する場合があります。

この大きな例は、Windows用に作成されたプログラムです。WinAPIはインターフェイスですが、多くの人が、Windows 95などの特定の実装のために機能するトリックを使用しました。これらのトリックは、プログラムを高速化したり、他の方法で必要だったよりも少ないコードで処理できるようにしたりする可能性があります。しかし、これらのトリックは、APIがWindows 2000で異なる方法で実装されていたため、プログラムがWindows 2000でクラッシュすることも意味していました。プログラムが十分に重要である場合、マイクロソフトは実際に先に進み、実装にハッキングを追加してプログラムが動作し続けるようにしますが、そのコストはWindowsコードの複雑さ(それに続くすべての問題)を増加させます。また、WinAPIを実装しようとするため、Wineの人々の生活はさらに難しくなりますが、これを行う方法についてはドキュメントのみを参照できます。


それは良い点であり、特定の状況でこれをよく耳にします。
Telastyn

あなたの言ってる事がわかります。あなたが言っていることを一般的なプログラミングに適応させることができるかどうかを見てみましょう:抽象クラスBの機能を使用するクラス(クラスA)があるとしましょう。クラスCとDはクラスBを継承します-それらはそのための具体的な実装を提供しますクラスが行うと言われています。クラスAがクラスCまたはDを直接使用する場合、「実装へのプログラミング」と呼ばれますが、これは非常に柔軟なソリューションではありません。ただし、クラスAがクラスBへの参照を使用している場合、それを後でC実装またはD実装に設定すると、より柔軟で保守しやすくなります。これは正しいです?
テルアビブコーン

これが正しい場合、私の質問よりも-「具体的なクラス参照ではなくインターフェイス参照を使用する」一般的な例以外に、「インターフェイスへのプログラミング」のより具体的な例はありますか?
テルアビブコーン

2
@AvivCohnこの返事は少し遅れましたが、具体的な例の1つはWorld Wide Webです。ブラウザー戦争(IE 4時代)の間に、Webサイトは仕様が述べていることではなく、一部のブラウザー(NetscapeまたはIE)の癖に向けて書かれました。これは基本的に、インターフェースの代わりに実装にプログラミングしていました。
セバスチャンレッド

9

私の個人的な経験についてのみ話すことができます。

最初の点は正しいです。柔軟性が得られるのは、呼び出されるべきではない具体的なクラスの実装の詳細を誤って呼び出すことができないためです。

たとえばILogger、具体的なLogToEmailLoggerクラスとして現在実装されているインターフェイスを考えてみましょう。このLogToEmailLoggerクラスはすべてのILoggerメソッドとプロパティを公開していますが、たまたま実装固有のプロパティも持っていますsysAdminEmail

アプリケーションでロガーを使用する場合、を設定するコードを消費することを心配しないでくださいsysAdminEmail。このプロパティは、ロガーのセットアップ時に設定する必要があり、世界から隠される必要があります。

具体的な実装に対してコーディングしている場合、ロガーを使用するときに実装プロパティを誤って設定する可能性があります。これで、アプリケーションコードはロガーと密に結合され、別のロガーに切り替えるには、最初に元のコードからコードを分離する必要があります。

この意味で、インターフェースへのコーディングは結合を緩めます。

2番目のポイントについて:インターフェイスへのコーディングで見たもう1つの理由は、コードの複雑さを軽減することです。

例えば、私は次のインタフェースでゲームを持っている想像しI2DRenderableI3DRenderableIUpdateable。1つのゲームコンポーネントに2Dと3Dの両方のレンダリング可能なコンテンツがあることは珍しくありません。他のコンポーネントは2Dのみで、他は3Dのみです。

1つのモジュールで2Dレンダリングが行われる場合、I2DRenderablesのコレクションを維持することは理にかなっています。コレクション内のオブジェクトが同じであるI3DRenderableIUpdateble、他のモジュールがオブジェクトのこれらの側面の処理を担当するかは関係ありません。

レンダリング可能なオブジェクトをリストとして保存するI2DRenderableと、レンダリングクラスの複雑さが低くなります。3Dレンダリングと更新ロジックは問題ではないため、子オブジェクトのこれらの側面は無視でき、無視する必要があります。

この意味で、インターフェースへのコーディングは、懸念分離することで複雑さを低く抑えます。


4

ここで使用されているインターフェイスという言葉には、おそらく2つの用法があります。主に質問で参照しているインターフェースJavaインターフェースです。これは具体的にはJavaの概念であり、より一般的にはプログラミング言語インターフェースです。

インターフェイスへのプログラミングはより広い概念だと思います。多くのWebサイトで利用できるようになった現在人気のあるREST APIは、より高いレベルのインターフェースへのプログラミングというより広範な概念のもう1つの例です。コードの内部の仕組みと外部の世界(インターネット上の人々、他のプログラム、同じプログラムの他の部分)の間にレイヤーを作成することにより、外部の世界を変えない限り、コード内のあらゆるものを変更できますそれが期待されている、それがインターフェースによって定義されている場合、またはあなたが名誉を与えるつもりの契約。

これにより、内部コードをリファクタリングする柔軟性が得られますが、インターフェイスに依存する他のすべてのことを伝える必要はありません。

また、コードをより安定させる必要があることも意味します。インターフェースにこだわることで、他の人のコードを壊してはなりません。インターフェイスを本当に変更する必要がある場合は、新しいバージョンのインターフェイスに重大な変更があることを知らせるAPIの新しいメジャーバージョン(1.abcから2.xyz)をリリースできます。

@Dovalがこの回答のコメントで指摘しているように、エラーの局所性もあります。これらはすべてカプセル化に帰着すると思います。オブジェクト指向設計のオブジェクトに使用するのと同じように、この概念はより高いレベルでも役立ちます。


1
通常見落とされる利点は、エラーの局所性です。Mapが必要で、バイナリツリーを使用してそれを実装するとします。これが機能するためには、キーに何らかの順序付けが必要であり、現在のノードのキーよりも「小さい」キーが左側のサブツリーにあり、「より大きい」キーがオンになっているという不変式を維持する必要があります正しいサブツリー。Mapの実装をインターフェースの背後に隠すと、Mapルックアップがうまくいかない場合、Mapモジュールにバグがあるはずだということがわかります。それが公開されている場合、バグはプログラムのどこにでもある可能性があります。私にとって、これは主な利点です。
ドーバル

4

現実の世界のアナロジーが役立つかもしれません:

インターフェースの主電源プラグ。
はい; テレビ、ラジオ、掃除機、洗濯機などの電源コードの端にある3本のピンで留められたもの

メインプラグがある(つまり、「メインプラグがある」インターフェイスを実装する)アプライアンスは、まったく同じ方法で処理できます。それらはすべて壁のソケットに差し込むことができ、そのソケットから電力を引き出すことができます。

個々のアプライアンスが行うことはまったく異なります。テレビでじゅうたんを掃除することはあまりありませんし、ほとんどの人は娯楽のために洗濯機を見ていません。しかし、これらのアプライアンスはすべて、壁のコンセントに接続できるという共通の動作を共有しています。

それが、インターフェースがあなたにもたらすものです。継承の複雑さを必要とせずに、多くの異なるオブジェクトクラスによって実行できる
統一された動作。


1

「インターフェースへのプログラミング」という用語は、多くの解釈に開かれています。ソフトウェア開発におけるインターフェースは非常に一般的な言葉です。ここに、私が長年にわたって訓練したジュニア開発者にこの概念を説明する方法を示します。

ソフトウェアアーキテクチャには、さまざまな自然境界があります。一般的な例は次のとおりです。

  • クライアントプロセスとサーバープロセス間のネットワーク境界
  • アプリケーションとサードパーティライブラリの間のAPI境界
  • プログラム内の異なるビジネスドメイン間の内部コード境界

重要なのは、これらの自然な境界が存在する場合、それらが識別され、その境界がどのように動作するかの契約が指定されることです。ソフトウェアをテストするのは、「反対側」が動作するかどうかではなく、相互作用が仕様に一致するかどうかです。

この結果は次のとおりです。

  • 外部コンポーネントは、仕様を実装している限り交換できます
  • 正しい動作を検証する単体テストの自然なポイント
  • 統合テストが重要になります-仕様はあいまいでしたか?
  • 開発者として、特定のタスクに取り組むとき、あなたは小さな懸念の世界を抱えています

これの多くはクラスとインターフェイスに関連する可能性がありますが、データモデル、ネットワークプロトコル、およびより一般的な方法で複数の開発者と連携することも理解することが重要です

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