web-dev-qa-db-ja.com

完全な不変性とオブジェクト指向プログラミング

ほとんどのOOP言語では、オブジェクトは一般に変更可能であり、例外のセットが限られています(Pythonのタプルや文字列など)。ほとんどの関数型言語では、データは不変です。

可変オブジェクトと不変オブジェクトのどちらにも、それぞれの長所と短所の完全なリストがあります。

のような両方の概念を融合させようとする言語があります。 scalaここには、(明示的に宣言された)可変で不変のデータがあります(私が間違っている場合は修正してください。scalaの知識は制限されています)。

私の質問は次のとおりです:Doescomplete(sic!)不変性-つまり、一度作成されたオブジェクトは変更できません-意味をなさないOOPコンテキスト?

そのようなモデルの設計または実装はありますか?

基本的に、(完全な)不変性であり、OOP反対または直交ですか?

動機:OOP通常はonデータを操作し、基礎となる情報を変更(変更)して、それらのオブジェクト間の参照を維持します。たとえば、メンバーPersonを持つクラスfatherのオブジェクト別のPersonオブジェクトを参照しています。親の名前を変更すると、これは子オブジェクトにすぐに表示され、更新する必要はありません。不変であるため、親と子の両方に新しいオブジェクトを作成する必要があります。共有オブジェクト、マルチスレッド、GILなどを使用したkerfuffle.

46
Hyperboreus

OOPと不変性は互いにほぼ完全に直交しています。ただし、命令型プログラミングと不変性はそうではありません。

OOPは2つのコア機能によって要約できます。

  • カプセル化:オブジェクトのコンテンツに直接アクセスするのではなく、特定のインターフェイス(「メソッド」)を介してこのオブジェクトと通信します。このインターフェースは、内部データを私から隠すことができます。技術的には、これはOOPではなくモジュラープログラミングに固有です。定義されたインターフェースを介してデータにアクセスすることは、抽象データ型とほぼ同じです。

  • 動的ディスパッチ:オブジェクトでメソッドを呼び出すと、実行されたメソッドは実行時に解決されます。 (たとえば、クラスベースのOOPでは、sizeインスタンスでIListメソッドを呼び出すことがありますが、その呼び出しはLinkedListクラスの実装に解決される場合があります)。動的ディスパッチは、ポリモーフィックな動作を可能にする1つの方法です。

カプセル化は、可変性がないとあまり意味がありません(内部stateがないため、外部干渉によって破損する可能性があります)が、すべてが不変である場合でも抽象化が容易になる傾向があります。

命令型プログラムは、順次実行されるステートメントで構成されています。ステートメントには、プログラムの状態を変更するなどの副作用があります。不変性では、状態を変更にすることはできません(もちろん新しい状態を作成できます)。したがって、命令型プログラミングは基本的に不変性と互換性がありません。

OOPは歴史的に常に命令型プログラミングに関連付けられており(シミュレーションはALGOLに基づいています)、すべての主流OOP言語には命令型の根がある(C++、 Java、C#、…はすべてCをルートとしています。これはOOP自体が命令型または変更可能であることを意味するものではありません。これは、OOP =これらの言語により、可変性が可能になります。

46
amon

オブジェクト指向のプログラマーの間には、カルチャーがあり、人々があなたがしていることを想定しているOOPほとんどのオブジェクト変更可能ですが、それはOOPrequiresmutability)とは別の問題です。また、その文化はゆっくりと見えるようです人々が関数型プログラミングに触れたことにより、より不変性に変化する。

Scalaは、オブジェクト指向に可変性が必要ないという非常に優れたイラストです。 Scalasupports可変性ですが、その使用はお勧めしません。慣用的なScalaは非常に多くのオブジェクト指向で、ほぼ完全に不変です。ほとんどの場合、Javaとの互換性のために可変性を実現します。特定の状況では、不変オブジェクトは非効率的であるか、操作が複雑です。

たとえば、 ScalaリストJavaリスト を比較します。 Scalaの不変リストには、Javaの可変リストと同じオブジェクトメソッドがすべて含まれています。さらに、実際には、Javaは sort などの演算に静的関数を使用し、Scalaはmapなどの関数形式のメソッドを追加するためです。 OOPのすべての特徴(カプセル化、継承、およびポリモーフィズム)は、オブジェクト指向のプログラマーが使い慣れた形式で利用でき、適切に使用されます。

表示される唯一の違いは、リストを変更すると、結果として新しいオブジェクトが取得されることです。多くの場合、変更可能なオブジェクトとは異なるデザインパターンを使用する必要がありますが、OOPをすべて破棄する必要はありません。

27
Karl Bielefeldt

不変性は、OOP言語で、オブジェクトアクセスポイントを、データを変更しないメソッドまたは読み取り専用プロパティとして公開するだけでシミュレートできます。不変性は、OOP一部の関数型言語の機能が欠落している可能性があることを除いて、他の関数型言語と同じように言語。

あなたの推定では、可変性はオブジェクト指向のコア機能であると思われます。しかし、可変性は単にオブジェクトまたは値のプロパティです。オブジェクト指向には、突然変異とはほとんどまたはまったく関係のない多くの固有の概念(カプセル化、ポリモーフィズム、継承など)が含まれており、すべてを不変にしても、これらの機能の利点を引き出すことができます。

すべての関数型言語が不変性を必要とするわけでもありません。 Clojureには、型を変更可能にする特定の注釈があり、「実用的な」関数型言語のほとんどには、変更可能な型を指定する方法があります。

「完全な不変性は命令的プログラミングで意味がありますか?」私は明白な答えを言うでしょうその質問にはありません。命令型プログラミングで完全な不変性を実現するには、再帰を優先してforループ(ループ変数を変更する必要があるため)のようなことを無視する必要があり、今や本質的にとにかく機能的な方法でプログラミングしています。

18
Robert Harvey

多くの場合、オブジェクトをカプセル化する値またはエンティティとして分類すると便利です。違いは、何かが値である場合、それへの参照を保持するコードは、コード自体が開始したのとは異なる方法で状態の変化を見ることはないということです。対照的に、エンティティへの参照を保持するコードは、参照保持者の制御を超えた方法で変更されることを期待する場合があります。

可変型または不変型のオブジェクトを使用してカプセル化された値を使用することは可能ですが、オブジェクトは次の条件の少なくとも1つが当てはまる場合にのみ値として動作できます。

  1. オブジェクトへの参照は、カプセル化された状態を変更する可能性のあるものにさらされることはありません。

  2. オブジェクトへの参照の少なくとも1つの所有者は、現存する参照が適用される可能性のあるすべての用途を知っています。

不変タイプのすべてのインスタンスは自動的に最初の要件を満たすため、それらを値として使用するのは簡単です。対照的に、可変タイプを使用するときにどちらかの要件を満たすことは、はるかに困難です。不変タイプへの参照は、その中にカプセル化された状態をカプセル化する手段として自由に渡すことができますが、可変タイプに格納された状態を渡すには、不変ラッパーオブジェクトを作成するか、プライベート保持オブジェクトによってカプセル化された状態を他のオブジェクトにコピーする必要があります。データの受信者によって提供または構築されます。

不変タイプは、値を渡すのに非常によく機能し、多くの場合、値の操作に少なくともいくらか使用できます。ただし、エンティティの処理はあまり得意ではありません。純粋に不変のタイプを持つシステム内のエンティティに最も近いものは、システムの状態が与えられると、その一部の属性を報告するか、またはのような新しいシステム状態インスタンスを生成する関数ですいくつかの選択可能な方法で異なる一部の特定の部分を除いて提供されます。さらに、エンティティの目的が、現実世界に存在する何かにいくつかのコードをインターフェイスさせることである場合、エンティティが変更可能な状態を公開することを回避することは不可能かもしれません。

たとえば、TCP接続を介してデータを受信した場合、古い「状態」への参照に影響を与えることなく、そのデータをバッファに含む新しい「世界の状態」オブジェクトを生成できます。データの最後のバッチを含まない世界の状態の古いコピーは欠陥があるため、実際の状態と一致しなくなるため使用しないでくださいTCPソケット。

5
supercat

C#では、一部の型は文字列のように不変です。

これはさらに、選択が強く検討されたことを示唆しているようです。

確かに、数十万回その型を変更する必要がある場合、不変の型を使用することは本当にパフォーマンスを要求します。この場合、StringBuilderクラスの代わりにstringクラスを使用することが推奨されるのはこのためです。

私はprofilerで実験を行いましたが、不変タイプを使用すると、実際にはより多くのCPUが必要になり、RAM要求が厳しくなります。

4000文字の文字列の1文字だけを変更するには、RAMの別の領域にすべての文字をコピーする必要があると考えると、直感的です。

4
Revious

すべての完全な不変性は、1つの非常に大きな理由により、OOP、またはその問題に関する他のほとんどのパラダイムではあまり意味がありません。

すべての便利なプログラムには副作用があります。

何も変更を引き起こさないプログラムは価値がありません。効果は同じであるため、実行する必要もありません。

何も変更しておらず、何らかの形で受け取った数値のリストを単純に合計している場合でも、結果を何かする必要があると考えてください。標準出力に出力するか、ファイルに書き込むか、またはどこでも。そして、それはバッファの変更とシステムの状態の変更を含みます。

変更可能にする必要のある部分の可変性を制限することは、非常に理にかなっています。しかし、何も変更する必要がない場合は、行う価値のあることは何もしていません。

0
cHao