「関数とデータ間の密結合」が悪いのはなぜですか?


38

この引用は、「Clojureの喜び」のp。32、しかし誰かが先週の夕食で私に同じことを言って、私はそれを他の場所でも聞いた:

[A]オブジェクト指向プログラミングの欠点は、関数とデータの間の密結合です。

アプリケーションで不要な結合が悪い理由を理解しています。また、オブジェクト指向プログラミングであっても、可変状態と継承を避けるべきだと言っています。しかし、なぜクラスに関数を貼り付けるのが本質的に悪いのかはわかりません。

つまり、クラスに関数を追加すると、Gmailでメールにタグを付けたり、ファイルをフォルダーに貼り付けたりするように思えます。それはあなたが再びそれを見つけるのを助ける組織的なテクニックです。いくつかの基準を選択してから、同じようなものをまとめます。OOP以前は、プログラムはファイル内のメソッドのかなり大きな袋でした。つまり、関数をどこかに配置する必要があります。整理してみませんか?

これが型に対するベール攻撃である場合、関数への入力および出力の型を制限することは間違っていると言うだけではありませんか?それに同意できるかどうかはわかりませんが、少なくともプロとコンタイプの安全性に関する議論には精通しています。これは、ほとんど別の懸念のように思えます。

確かに、時々それを間違え、機能を間違ったクラスに置きます。しかし、他の間違いと比較すると、これは非常に小さな不便さのようです。

そのため、Clojureには名前空間があります。OOPのクラスに関数を貼り付けることは、Clojureの名前空間に関数を貼り付けることとどのように異なりますか?クラスの関数は、必ずしもそのクラスのメンバーだけで動作するとは限らないことを忘れないでください。java.lang.StringBuilderを見てください。これは、すべての参照型で動作するか、オートボクシングを通じて、すべての型で動作します。

PSこの引用は、私が読んだことのない本を参照しています:Multiparadigm Programming in Leda:Timothy Budd、1995


20
筆者は単にOOPを適切に理解しておらず、Javaが悪く、Clojureが優れていると言うもう1つの理由が必要だったと思います。/暴言
陶酔

6
インスタンスメソッドは(無料の関数や拡張メソッドとは異なり)他のモジュールから追加できません。インスタンスメソッドによってのみ実装できるインターフェイスを検討する場合、これはより多くの制限になります。異なるモジュールでインターフェースとクラスを定義してから、3番目のモジュールのコードを使用してそれらをバインドすることはできません。haskellの型クラスのような、より柔軟なアプローチでそれが可能になります。
CodesInChaos

4
@Euphoric筆者理解していたと思いますが、Clojureコミュニティは、OOPのストローマンを作成し、適切なガベージコレクション、大量のメモリ、高速プロセッサ、および多くのディスク容量。私は彼らがOOPでの暴行をやめ、本当の原因、例えばフォンノイマンアーキテクチャをターゲットにしたいと思います。
グレンペターソン

4
私の印象では、OOPに対する批判のほとんどは、実際にはJavaで実装されたOOPに対する批判です。それが故意のストローマンだからではなく、彼らがOOPに関連付けているからです。静的型付けについて不平を言う人々には、かなり似た問題があります。ほとんどの問題はコンセプトに固有のものではなく、そのコンセプトの一般的な実装の欠陥です。
-CodesInChaos

3
タイトルが質問の本文と一致しません。関数とデータの密結合が悪い理由を説明するのは簡単ですが、テキストでは「OOPはこれを実行しますか?」、「もしそうなら、なぜですか?」という質問をします。「これは悪いことですか?」これまでのところ、これら3つの質問のうちの1つ以上に対処する回答を受け取ることができたのは幸運であり、タイトルのより単純な質問を想定している人はいませんでした。
-itsbruce

回答:


34

理論的には、緩やかな関数とデータの結合により、同じデータで機能する関数を追加するのが簡単になります。欠点は、データ構造自体を変更することがより困難になることです。そのため、実際には、適切に設計された機能コードと適切に設計されたOOPコードは、非常に類似したレベルの結合を持っています。

データ構造の例として、有向非巡回グラフ(DAG)を取り上げます。関数型プログラミングでは、繰り返しを避けるために抽象化が必要なので、ノードとエッジの追加と削除、特定のノードから到達可能なノードの検索、トポロジカルソートの作成などの機能を備えたモジュールを作成します。コンパイラーがデータを強制していなくても、データに効果的に密結合しています。難しい方法でノードを追加できますが、なぜ追加したいのですか?1つのモジュール内の凝集性により、システム全体の密結合が防止されます。

逆に、OOP側では、DAGオブジェクトをパラメーターとして渡して、基本的なDAG操作以外の機能を個別の「ビュー」クラスで実行します。DAGデータを操作するビューを必要な数だけ追加するのは簡単で、関数型プログラムで見られるのと同じレベルの関数データの分離を作成します。コンパイラは、すべてを1つのクラスに詰め込むことを妨げませんが、同僚はそうします。

プログラミングパラダイムを変更しても、抽象化、結合、結合のベストプラクティスは変更されません。コンパイラーが実施するのに役立つプラクティスが変更されるだけです。関数型プログラミングでは、関数とデータの結合が必要な場合は、コンパイラーではなく紳士の同意によって強制されます。OOPでは、モデルビューの分離は、コンパイラーではなく紳士の同意によって実施されます。


13

まだ知らなかった場合は、この洞察が必要です。オブジェクト指向とクロージャーの概念は、同じコインの両面です。とはいえ、閉鎖とは何ですか?周囲のスコープから変数またはデータを取得して関数内でバインドするか、オブジェクト指向の観点から、たとえばコンストラクタに何かを渡すときに効果的に同じことを行い、後でそれを使用できるようにしますそのインスタンスのメンバー関数のデータ。しかし、周囲のスコープから物を取り出すのは良いことではありません-周囲のスコープが大きいほど、これを行うのはより邪悪です(実際には、作業を完了するにはいくつかの悪が必要です)。グローバル変数を使用すると、これが極端になり、プログラム内の関数がプログラムスコープで変数を使用します。があるグローバル変数が悪である理由に関する他の場所での良い説明

オブジェクト指向の手法に従う場合、プログラム内のすべてのモジュールが特定の最小レベルの悪を持っていることを基本的にすでに受け入れています。プログラミングに機能的なアプローチをとる場合、プログラムにはクロージャーの悪が含まれない理想的なものを目指していますが、一部のモジュールにはまだあるかもしれませんが、オブジェクト指向はオブジェクト指向よりはるかに少ないでしょう。

それはオブジェクト指向の欠点です-クロージャー標準(プログラミングの壊れたウィンドウ理論の一種)を作成することにより、この種の悪、データの機能への結合を促進します

唯一のプラス面は、最初に多くのクロージャーを使用することを知っていた場合、OOは少なくとも平均的なプログラマーが理解できるようにそのアプローチを整理するのに役立つアイデア的なフレームワークを提供することです。特に、閉じられる変数は、関数のクロージャーで暗黙的に取得されるのではなく、コンストラクターで明示されます。多くのクロージャを使用する関数型プログラムは、同等のオブジェクト指向プログラムよりも多くの場合、よりわかりにくいですが、必ずしもエレガントではありません:)


8
今日の引用:「仕事を
終わらせるに

5
あなたは、悪と呼ぶものがなぜ悪なの、本当に説明していません。あなたは彼らを悪と呼んでいます。なぜ彼らが悪であるかを説明してください。紳士の質問に答えがあるかもしれません。
ロバートハーベイ

2
ただし、最後の段落で答えを保存します。あなたによると、それは唯一のプラス面かもしれませんが、それは小さなことではありません。いわゆる「平均的なプログラマー」は、実際に一定の儀式を歓迎します。
ロバートハーヴェイ

オブジェクト指向とクロージャーが同義語である場合、なぜ多くのオブジェクト指向言語がそれらの明示的なサポートを提供できなかったのですか?あなたが引用するC2 wikiページには、そのサイトの通常の論争よりもさらに多くの論争があります(そしてコンセンサスも少なくなっています)。
-itsbruce

1
@itsbruce彼らはほとんど不要になりました。「クローズド」される変数は、代わりにオブジェクトに渡されるクラス変数になります。
イズカタ

7

結合についてです:

オブジェクトに組み込まれてそのオブジェクトで機能する関数は、他のタイプのオブジェクトでは使用できません。

Haskellでは、型クラスに対して機能する関数を作成します。したがって、関数が機能する特定のクラスのタイプである限り、特定の関数が機能できるオブジェクトにはさまざまなタイプがあります。

独立型の関数を使用すると、タイプAの内部で動作するように関数を書くことに集中した場合に得られない分離が可能になります。それ以外の場合は、タイプBインスタンスまたはタイプCインスタンスで使用するのに十分一般的です。


3
それがインターフェースのポイントではありませんか?タイプBとタイプCがあなたの関数と同じように見えることを可能にするものを提供するために、複数のタイプで動作することができますか?
Random832

2
@ Random832絶対に、しかしなぜそのデータ型で動作しないのにデータ型の内部に関数を埋め込むのですか?答え:関数をデータ型埋め込む唯一の理由です。静的クラスのみを記述し、すべての関数がカプセル化されているデータ型を気にせずに、所有する型から完全に切り離すことができますが、なぜそれらを型に入れるのですか?機能的アプローチ:気にせず、ある種のインターフェースに向けて機能するように関数を記述してください。そうすれば、それらをデータでカプセル化する理由はありません。
ジミー・ホッファ

まだインターフェースを実装する必要があります。
Random832

2
@ Random832インターフェースはデータ型です。それらにカプセル化された関数は必要ありません。無料の関数を使用すると、すべてのインターフェイスは、関数が機能するために使用できるデータを賞賛する必要があります。
ジミー・ホッファ

2
@ Random832は、オブジェクト指向で非常に一般的であるように、実世界のオブジェクトに関連付けるために、本のインターフェースを考えます。情報(データ)を提示します。ページを持つタイプのクラスに対して機能するページめくりの無料機能があります。この機能は、あらゆる種類の本、新聞、K-Martのポスタースピンドル、グリーティングカード、メール、ステープルでつながれたものに対して機能します。コーナー。本のメンバーとしてページめくりを実装した場合、ページめくりを使用できるすべてのことを逃してしまいます。これはバインドされていない無料の機能です。ビールにPartyFoulExceptionをスローするだけです。
ジミー・ホッファ

4

Javaおよび同様のOOPの化身では、インスタンスメソッドは(無料の関数や拡張メソッドとは異なり)他のモジュールから追加できません。

インスタンスメソッドによってのみ実装できるインターフェイスを検討する場合、これはより多くの制限になります。異なるモジュールでインターフェースとクラスを定義してから、3番目のモジュールのコードを使用してそれらをバインドすることはできません。Haskellの型クラスのような、より柔軟なアプローチでそれが可能になります。


Scalaで簡単にできます。私はGoに精通していませんが、知っている限り、そこでもそれを行うことができます。Rubyでは、オブジェクトにメソッドを追加してから、それらを何らかのインターフェースに適合させることも非常に一般的な方法です。あなたが説明するものは、OOにリモートでさえ関連するものよりも、設計が不適切なタイプシステムのように見えます。思考実験のように、オブジェクトではなく抽象データ型について話すとき、あなたの答えはどう違うでしょうか?私はそれが違いを生むとは思わない、それはあなたの議論がオブジェクト指向と無関係であることを証明するだろう。
ヨルグWミットタグ

1
@JörgWMittag代数的データ型を意味すると思います。そして、CodesInChaos、Haskellはあなたの提案を非常に明確に落胆させます。孤立したインスタンスと呼ばれ、GHCで警告を発行します。
-jozefg

3
@JörgWMittag私の印象では、OOPを批判する人の多くは、Javaや類似の言語で使用されるOOPの形式を厳格なクラス構造で批判し、インスタンスメソッドに焦点を当てています。その引用の私の印象は、インスタンスメソッドに焦点を当てていることを批判し、golangが使用しているようなOOPの他のフレーバーには実際には適用されないということです。
CodesInChaos

2
@CodesInChaosその後、おそらくこれを「静的クラスベースのオブジェクト指向」として明確にする
-jozefg

@jozefg:抽象データ型について話しています。代数的データ型がこの議論にどのように関連しているかはわかりません。
ヨルグWミットタグ

3

オブジェクト指向は、基本的に手続き型データ抽象化(または直交問題である副作用を取り除く場合は機能データ抽象化)に関するものです。ある意味では、Lambda Calculusは、機能データの抽象化のみを提供するため、最も古く、最も純粋なオブジェクト指向言語です(関数以外に構造を持たないため)。

単一のオブジェクトの操作のみが、そのオブジェクトのデータ表現を検査できます。同じタイプの他のオブジェクトでさえそれを行うことはできません。(これがオブジェクト指向データ抽象化と抽象データ型の主な違いです。ADTを使用すると、同じ型のオブジェクトは互いのデータ表現を検査でき、他の型のオブジェクトの表現のみが非表示になります。)

これが意味することは、同じタイプの複数のオブジェクトが異なるデータ表現を持つ場合があるということです。まったく同じオブジェクトでも、異なる時間に異なるデータ表現を持つ場合があります。(たとえば、Scalaでは、MapsとSetsは要素の数に応じて配列とハッシュトライを切り替えます。これは、定数が非常に小さいため、配列内の線形検索が検索ツリーの対数検索よりも高速であるためです。 )

オブジェクトの外側からは、そのデータ表現を知ることはできません。これは、密結合の反対です。


OOPには、状況に応じて内部データ構造を切り替えるクラスがあるため、これらのクラスのオブジェクトインスタンスは、非常に異なるデータ表現を同時に使用できます。基本的なデータの隠蔽とカプセル化 では、ScalaのMapは、OOP言語の適切に実装された(wrtデータの非表示およびカプセル化)Mapクラスとどのように違いますか?
マルジャンヴェネマ

この例では、クラス内のアクセサー関数でデータをカプセル化する(したがって、それらの関数をそのデータに密結合する)ことにより、実際にそのクラスのインスタンスをプログラムの残りの部分と疎結合できます。あなたは引用の中心点に反論しています-非常に素晴らしい!
グレンペターソン

2

データと関数の間の密結合は悪いものです。お互いに独立して変更できるようにしたいので、密結合はこれを難しくします。

関数に表示されるさまざまなデータが関数の変更を必要とせず、同様に、それらの関数の変更をサポートするために操作中のデータを変更することなく関数を変更できるようにする必要があります。


1
はい、欲しいです。しかし、私の経験では、処理するように明示的に設計されていない非自明な関数にデータを送信すると、その関数は壊れる傾向があります。型の安全性だけでなく、関数の作成者が予期していなかったデータ条件についても言及しています。関数が古く、頻繁に使用されている場合、新しいデータが通過できるように変更すると、まだ機能する必要がある古い形式のデータで機能が破損する可能性があります。デカップリングは関数対データにとって理想的かもしれませんが、そのデカップリングの現実は困難で危険な場合があります。
グレンペターソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.