web-dev-qa-db-ja.com

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

私はこの引用をp。の " The Joy of Clojure "で見つけました。 32、しかし誰かが先週の夕食で同じことを私に言った、そして私はそれを他の場所でも聞いた:

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

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

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

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

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

したがって、Clojureには名前空間があります。 OOP=のクラスに関数を貼り付けるのは、Clojureの名前空間に関数を貼り付けるのとどう違うのですか?なぜそんなに悪いのですか?クラスの関数は必ずしもメンバーだけで機能するわけではないことに注意してくださいそのクラスのJava.lang.StringBuilderを見てください-それはすべての参照型で、またはオートボクシングを通じて、すべての型で動作します。

追伸この引用は、私が読んでいない本を参照しています: Ledaのマルチパラダイムプログラミング:Timothy Budd、1995

38
GlenPeterson

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

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

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

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

34
Karl Bielefeldt

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

OOテクニックを使用する場合、基本的にはプログラム内のすべてのモジュールに特定の最低レベルの悪があることをすでに受け入れています。プログラミングに機能的アプローチをとれば、プログラムのモジュールにはクロージャの悪が含まれていますが、まだいくつかありますが、オブジェクト指向よりはるかに少ないでしょう。

これがOOのマイナス面です。これは、クロージャを標準化することで機能するデータのこの種の悪、結合を促進します(一種の 壊れたウィンドウ理論 プログラミング)。

唯一のプラス面は、最初に多くのクロージャーを使用することを知っていた場合、OOは、平均的なプログラマーが特に、閉じられる変数は、関数クロージャで暗黙的に取得されるのではなく、コンストラクタで明示されます。多くのクロージャを使用する関数型プログラムは、多くの場合、同等のものよりも不可解ですOOプログラム、必ずしも必ずしもエレガントではありません:)

13
Benedict

typeカップリングについてです:

そのオブジェクトを処理するためにオブジェクトに組み込まれた関数は、他のタイプのオブジェクトでは使用できません。

Haskellでは、タイプクラスに対して機能する関数を記述します。そのため、特定の関数がタイプである限り、さまざまなタイプのオブジェクトが存在します。指定されたclassの関数が動作します。

独立した関数は、タイプAの内部で機能するように関数の作成に焦点を当てた場合に得られないような分離を可能にします。そのため、関数がタイプAのインスタンスを持たない場合、関数は使用できませんそれ以外の場合は、タイプBインスタンスまたはタイプCインスタンスで使用するのに十分一般的です。

7
Jimmy Hoffa

JavaおよびOOPの類似の化身)では、(フリー関数や拡張メソッドとは異なり)インスタンスメソッドを他のモジュールから追加することはできません。

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

4
CodesInChaos

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

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

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

オブジェクトの外側からは、すべきではありませんできないデータ表現を知っています。それが密結合の反対です。

3
Jörg W Mittag

データと関数のタイトカップリングは、お互いに独立して変更できるようにする必要があるため不良であり、タイトカップリングは、もう一方の知識がなく、場合によっては変更できないため、一方を変更できないため、これを困難にします。

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

2
Michael Durrant